From a7dc080ae5d87b431353d6d9b05e44c5f217a92c Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 10:40:06 -0600 Subject: [PATCH 01/30] initial run --- spec/FilesController.spec.js | 3 +++ spec/helper.js | 2 ++ spec/support/jasmine.json | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/FilesController.spec.js b/spec/FilesController.spec.js index 5b6a3d4ab2..996e4c3961 100644 --- a/spec/FilesController.spec.js +++ b/spec/FilesController.spec.js @@ -80,6 +80,9 @@ describe('FilesController', () => { expect(typeof error).toBe('object'); expect(error.message.indexOf('biscuit')).toBe(13); expect(error.code).toBe(Parse.Error.INVALID_FILE_NAME); + mockAdapter.validateFilename = () => { + return null; + }; done(); }); diff --git a/spec/helper.js b/spec/helper.js index 68254f518f..15577607de 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -28,6 +28,7 @@ if (global._babelPolyfill) { process.noDeprecation = true; const cache = require('../lib/cache').default; +const defaults = require('../lib/defaults').default; const ParseServer = require('../lib/index').ParseServer; const path = require('path'); const TestUtils = require('../lib/TestUtils'); @@ -205,6 +206,7 @@ afterEach(function (done) { done(); }; Parse.Cloud._removeAllHooks(); + defaults.protectedFields = { _User: { '*': ['email'] } }; databaseAdapter .getAllClasses() .then(allSchemas => { diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json index 1fbe0c31bf..84d7629c1b 100644 --- a/spec/support/jasmine.json +++ b/spec/support/jasmine.json @@ -2,5 +2,5 @@ "spec_dir": "spec", "spec_files": ["*spec.js"], "helpers": ["helper.js"], - "random": false + "random": true } From 54744a22c89cbc60a619ea9f3434fb54868e5cbf Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 11:43:20 -0600 Subject: [PATCH 02/30] Update ParseGraphQLServer.spec.js --- spec/ParseGraphQLServer.spec.js | 239 ++++++++++++++++---------------- 1 file changed, 118 insertions(+), 121 deletions(-) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 67bac737b7..ec26f8ad9e 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -9033,7 +9033,7 @@ describe('ParseGraphQLServer', () => { it('should support object values', async () => { try { - const someFieldValue = { + const someObjectFieldValue = { foo: { bar: 'baz' }, number: 10, }; @@ -9048,7 +9048,7 @@ describe('ParseGraphQLServer', () => { `, variables: { schemaFields: { - addObjects: [{ name: 'someField' }], + addObjects: [{ name: 'someObjectField' }], }, }, context: { @@ -9057,11 +9057,10 @@ describe('ParseGraphQLServer', () => { }, }, }); - await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('Object'); + expect(schema.fields.someObjectField.type).toEqual('Object'); const createResult = await apolloClient.mutate({ mutation: gql` @@ -9075,13 +9074,13 @@ describe('ParseGraphQLServer', () => { `, variables: { fields: { - someField: someFieldValue, + someObjectField: someObjectFieldValue, }, }, }); const where = { - someField: { + someObjectField: { equalTo: { key: 'foo.bar', value: 'baz' }, notEqualTo: { key: 'foo.bar', value: 'bat' }, greaterThan: { key: 'number', value: 9 }, @@ -9093,13 +9092,13 @@ describe('ParseGraphQLServer', () => { query GetSomeObject($id: ID!, $where: SomeClassWhereInput) { someClass(id: $id) { id - someField + someObjectField } someClasses(where: $where) { edges { node { id - someField + someObjectField } } } @@ -9113,13 +9112,13 @@ describe('ParseGraphQLServer', () => { const { someClass: getResult, someClasses } = queryResult.data; - const { someField } = getResult; - expect(typeof someField).toEqual('object'); - expect(someField).toEqual(someFieldValue); + const { someObjectField } = getResult; + expect(typeof someObjectField).toEqual('object'); + expect(someObjectField).toEqual(someObjectFieldValue); // Checks class query results expect(someClasses.edges.length).toEqual(1); - expect(someClasses.edges[0].node.someField).toEqual(someFieldValue); + expect(someClasses.edges[0].node.someObjectField).toEqual(someObjectFieldValue); } catch (e) { handleError(e); } @@ -9127,11 +9126,11 @@ describe('ParseGraphQLServer', () => { it('should support object composed queries', async () => { try { - const someFieldValue = { + const someObjectFieldValue1 = { lorem: 'ipsum', number: 10, }; - const someFieldValue2 = { + const someObjectFieldValue2 = { foo: { test: 'bar', }, @@ -9144,7 +9143,7 @@ describe('ParseGraphQLServer', () => { createClass( input: { name: "SomeClass" - schemaFields: { addObjects: [{ name: "someField" }] } + schemaFields: { addObjects: [{ name: "someObjectField" }] } } ) { clientMutationId @@ -9180,10 +9179,10 @@ describe('ParseGraphQLServer', () => { `, variables: { fields1: { - someField: someFieldValue, + someObjectField: someObjectFieldValue1, }, fields2: { - someField: someFieldValue2, + someObjectField: someObjectFieldValue2, }, }, }); @@ -9191,24 +9190,24 @@ describe('ParseGraphQLServer', () => { const where = { AND: [ { - someField: { + someObjectField: { greaterThan: { key: 'number', value: 9 }, }, }, { - someField: { + someObjectField: { lessThan: { key: 'number', value: 11 }, }, }, { OR: [ { - someField: { + someObjectField: { equalTo: { key: 'lorem', value: 'ipsum' }, }, }, { - someField: { + someObjectField: { equalTo: { key: 'foo.test', value: 'bar' }, }, }, @@ -9223,7 +9222,7 @@ describe('ParseGraphQLServer', () => { edges { node { id - someField + someObjectField } } } @@ -9241,11 +9240,11 @@ describe('ParseGraphQLServer', () => { const { edges } = someClasses; expect(edges.length).toEqual(2); expect( - edges.find(result => result.node.id === create1.someClass.id).node.someField - ).toEqual(someFieldValue); + edges.find(result => result.node.id === create1.someClass.id).node.someObjectField + ).toEqual(someObjectFieldValue1); expect( - edges.find(result => result.node.id === create2.someClass.id).node.someField - ).toEqual(someFieldValue2); + edges.find(result => result.node.id === create2.someClass.id).node.someObjectField + ).toEqual(someObjectFieldValue2); } catch (e) { handleError(e); } @@ -9253,7 +9252,7 @@ describe('ParseGraphQLServer', () => { it('should support array values', async () => { try { - const someFieldValue = [1, 'foo', ['bar'], { lorem: 'ipsum' }, true]; + const someArrayFieldValue = [1, 'foo', ['bar'], { lorem: 'ipsum' }, true]; await apolloClient.mutate({ mutation: gql` @@ -9265,7 +9264,7 @@ describe('ParseGraphQLServer', () => { `, variables: { schemaFields: { - addArrays: [{ name: 'someField' }], + addArrays: [{ name: 'someArrayField' }], }, }, context: { @@ -9278,7 +9277,7 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); const schema = await new Parse.Schema('SomeClass').get(); - expect(schema.fields.someField.type).toEqual('Array'); + expect(schema.fields.someArrayField.type).toEqual('Array'); const createResult = await apolloClient.mutate({ mutation: gql` @@ -9292,7 +9291,7 @@ describe('ParseGraphQLServer', () => { `, variables: { fields: { - someField: someFieldValue, + someArrayField: someArrayFieldValue, }, }, }); @@ -9301,17 +9300,17 @@ describe('ParseGraphQLServer', () => { query: gql` query GetSomeObject($id: ID!) { someClass(id: $id) { - someField { + someArrayField { ... on Element { value } } } - someClasses(where: { someField: { exists: true } }) { + someClasses(where: { someArrayField: { exists: true } }) { edges { node { id - someField { + someArrayField { ... on Element { value } @@ -9326,9 +9325,9 @@ describe('ParseGraphQLServer', () => { }, }); - const { someField } = getResult.data.someClass; - expect(Array.isArray(someField)).toBeTruthy(); - expect(someField.map(element => element.value)).toEqual(someFieldValue); + const { someArrayField } = getResult.data.someClass; + expect(Array.isArray(someArrayField)).toBeTruthy(); + expect(someArrayField.map(element => element.value)).toEqual(someArrayFieldValue); expect(getResult.data.someClasses.edges.length).toEqual(1); } catch (e) { handleError(e); @@ -10201,101 +10200,99 @@ describe('ParseGraphQLServer', () => { let apolloClient; beforeEach(async () => { - if (!httpServer) { - 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', - }, + 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', }, - }), - 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(); - }, + }, + }), + 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, }, - }), - types: [ - new GraphQLInputObjectType({ - name: 'CreateSomeClassFieldsInput', - fields: { - type: { type: TypeEnum }, + customQueryWithAutoTypeReturn: { + type: SomeClassType, + args: { + id: { type: new GraphQLNonNull(GraphQLString) }, }, - }), - new GraphQLInputObjectType({ - name: 'UpdateSomeClassFieldsInput', - fields: { - type: { type: TypeEnum }, + resolve: async (p, { id }) => { + const obj = new Parse.Object('SomeClass'); + obj.id = id; + await obj.fetch(); + return obj.toJSON(); }, - }), - SomeClassType, - ], + }, + }, }), - }); - - parseGraphQLServer.applyGraphQL(expressApp); - await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve)); - const httpLink = createUploadLink({ - uri: 'http://localhost:13377/graphql', - fetch, - headers, + types: [ + new GraphQLInputObjectType({ + name: 'CreateSomeClassFieldsInput', + fields: { + type: { type: TypeEnum }, + }, + }), + new GraphQLInputObjectType({ + name: 'UpdateSomeClassFieldsInput', + fields: { + type: { type: TypeEnum }, + }, + }), + SomeClassType, + ], + }), }); - 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 () => { + afterEach(async () => { await httpServer.close(); }); From f6c5a62429fff84a2543c6152b8055560e4c1945 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 12:14:17 -0600 Subject: [PATCH 03/30] temporarily enable reporter --- spec/helper.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/helper.js b/spec/helper.js index 15577607de..c4e8e13298 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -5,10 +5,10 @@ const CurrentSpecReporter = require('./support/CurrentSpecReporter.js'); // Sets up a Parse API server for testing. 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()); -} +// 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) { From 2aabbbc1a03f4f9fed82a75889b09e2830ab1176 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 12:41:47 -0600 Subject: [PATCH 04/30] Bump retry limit --- spec/GridFSBucketStorageAdapter.spec.js | 6 +++--- spec/helper.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/GridFSBucketStorageAdapter.spec.js b/spec/GridFSBucketStorageAdapter.spec.js index 92f7aae388..8431d6d7f5 100644 --- a/spec/GridFSBucketStorageAdapter.spec.js +++ b/spec/GridFSBucketStorageAdapter.spec.js @@ -4,7 +4,6 @@ const GridFSBucketAdapter = require('../lib/Adapters/Files/GridFSBucketAdapter') const { randomString } = require('../lib/cryptoUtils'); const databaseURI = 'mongodb://localhost:27017/parse'; const request = require('../lib/request'); -const Config = require('../lib/Config'); async function expectMissingFile(gfsAdapter, name) { try { @@ -395,8 +394,9 @@ describe_only_db('mongo')('GridFSBucket and GridStore interop', () => { }); it('should handle getMetadata error', async () => { - const config = Config.get('test'); - config.filesController.getMetadata = () => Promise.reject(); + const gfsAdapter = new GridFSBucketAdapter(databaseURI); + await reconfigureServer({ filesAdapter: gfsAdapter }); + gfsAdapter.getMetadata = () => Promise.reject(); const headers = { 'X-Parse-Application-Id': 'test', diff --git a/spec/helper.js b/spec/helper.js index c4e8e13298..588b072adc 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -147,7 +147,7 @@ const reconfigureServer = (changedConfiguration = {}) => { if (error) { reject(error); } else { - Parse.CoreManager.set('REQUEST_ATTEMPT_LIMIT', 1); + Parse.CoreManager.set('REQUEST_ATTEMPT_LIMIT', 3); resolve(parseServer); } }, From 968c534c5ebb3a507274e809eeae251774817a00 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 13:14:29 -0600 Subject: [PATCH 05/30] fix undefined database --- src/PromiseRouter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PromiseRouter.js b/src/PromiseRouter.js index 1f531025a9..fd5ffb0372 100644 --- a/src/PromiseRouter.js +++ b/src/PromiseRouter.js @@ -214,7 +214,7 @@ function maskSensitiveUrl(req) { } function clearSchemaCache(req) { - if (req.config && !req.config.enableSingleSchemaCache) { + if (req && req.config && req.config.database && !req.config.enableSingleSchemaCache) { req.config.database.schemaCache.clear(); } } From 37c10545e0b8a28be72153dc2c9f7fd1c6a4a8b1 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 14:30:50 -0600 Subject: [PATCH 06/30] try to catch error --- spec/PushController.spec.js | 2 +- src/Config.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 873116bcd4..b166cbc7e8 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -1237,7 +1237,7 @@ describe('PushController', () => { const auth = { isMaster: true }; const pushController = new PushController(); - let config = Config.get(Parse.applicationId); + let config; const pushes = []; const pushAdapter = { diff --git a/src/Config.js b/src/Config.js index 521f354d8e..a69ce24ff2 100644 --- a/src/Config.js +++ b/src/Config.js @@ -29,7 +29,8 @@ export class Config { static get(applicationId: string, mount: string) { const cacheInfo = AppCache.get(applicationId); if (!cacheInfo) { - return; + console.trace(); + throw '---------------Empty Cache-------------------'; } const config = new Config(); config.applicationId = applicationId; From 2d237ef67d19433ef34a154c59f708c6a89c71dd Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 15:40:06 -0600 Subject: [PATCH 07/30] Handle LiveQueryServers --- .github/workflows/ci.yml | 5 +++++ spec/CLI.spec.js | 6 +++++ spec/ParseLiveQueryServer.spec.js | 10 +++++++-- spec/PushController.spec.js | 37 ++++++++++++++----------------- spec/helper.js | 4 +++- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92ead63551..7a159401fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,6 +97,7 @@ jobs: MONGODB_TOPOLOGY: standalone MONGODB_STORAGE_ENGINE: wiredTiger NODE_VERSION: 15.11.0 + fail-fast: false name: ${{ matrix.name }} timeout-minutes: 30 runs-on: ubuntu-18.04 @@ -145,6 +146,7 @@ jobs: POSTGRES_IMAGE: postgis/postgis:12-3.0 - name: Postgres 13, Postgis 3.1 POSTGRES_IMAGE: postgis/postgis:13-3.1 + fail-fast: false name: ${{ matrix.name }} timeout-minutes: 30 runs-on: ubuntu-18.04 @@ -186,4 +188,7 @@ jobs: - run: npm run coverage env: CI: true + - name: Setup SSH Session + if: ${{ failure() }} + uses: mxschmitt/action-tmate@v3 - run: bash <(curl -s https://codecov.io/bash) diff --git a/spec/CLI.spec.js b/spec/CLI.spec.js index 0cd54c2e8b..540f02abf6 100644 --- a/spec/CLI.spec.js +++ b/spec/CLI.spec.js @@ -227,6 +227,8 @@ describe('execution', () => { 'test', '--databaseURI', 'mongodb://localhost/test', + '--port', + '1339', ]); childProcess.stdout.on('data', data => { data = data.toString(); @@ -247,6 +249,8 @@ describe('execution', () => { 'test', '--databaseURI', 'mongodb://localhost/test', + '--port', + '1340', '--mountGraphQL', ]); let output = ''; @@ -271,6 +275,8 @@ describe('execution', () => { 'test', '--databaseURI', 'mongodb://localhost/test', + '--port', + '1341', '--mountGraphQL', '--mountPlayground', ]); diff --git a/spec/ParseLiveQueryServer.spec.js b/spec/ParseLiveQueryServer.spec.js index 66fae587d8..5e9c71d731 100644 --- a/spec/ParseLiveQueryServer.spec.js +++ b/spec/ParseLiveQueryServer.spec.js @@ -127,7 +127,10 @@ describe('ParseLiveQueryServer', function () { serverStartComplete: () => { expect(parseServer.liveQueryServer).not.toBeUndefined(); expect(parseServer.liveQueryServer.server).toBe(parseServer.server); - parseServer.server.close(done); + parseServer.server.close(async () => { + await reconfigureServer(); + done(); + }); }, }); }); @@ -149,7 +152,10 @@ describe('ParseLiveQueryServer', function () { expect(parseServer.liveQueryServer).not.toBeUndefined(); expect(parseServer.liveQueryServer.server).not.toBe(parseServer.server); parseServer.liveQueryServer.server.close( - parseServer.server.close.bind(parseServer.server, done) + parseServer.server.close.bind(parseServer.server, async () => { + await reconfigureServer(); + done(); + }) ); }, }); diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index b166cbc7e8..775ed7cb5b 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -568,7 +568,7 @@ describe('PushController', () => { await pushCompleted(pushStatusId); }); - it('should properly report failures in _PushStatus', done => { + it('should properly report failures in _PushStatus', async () => { const pushAdapter = { send: function (body, installations) { return installations.map(installation => { @@ -593,30 +593,27 @@ describe('PushController', () => { badge: 1, }, }; - const config = Config.get(Parse.applicationId); const auth = { isMaster: true, }; const pushController = new PushController(); - reconfigureServer({ + await reconfigureServer({ push: { adapter: pushAdapter }, - }) - .then(() => { - return pushController.sendPush(payload, where, config, auth); - }) - .then(() => { - fail('should not succeed'); - done(); - }) - .catch(() => { - const query = new Parse.Query('_PushStatus'); - query.find({ useMasterKey: true }).then(results => { - expect(results.length).toBe(1); - const pushStatus = results[0]; - expect(pushStatus.get('status')).toBe('failed'); - done(); - }); - }); + }); + const config = Config.get(Parse.applicationId); + try { + await pushController.sendPush(payload, where, config, auth); + fail(); + } catch (e) { + const query = new Parse.Query('_PushStatus'); + let results = await query.find({ useMasterKey: true }); + while (results.length === 0) { + results = await query.find({ useMasterKey: true }); + } + expect(results.length).toBe(1); + const pushStatus = results[0]; + expect(pushStatus.get('status')).toBe('failed'); + } }); it('should support full RESTQuery for increment', async () => { diff --git a/spec/helper.js b/spec/helper.js index 588b072adc..2b03f31d3d 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -83,7 +83,7 @@ if (process.env.PARSE_SERVER_LOG_LEVEL) { logLevel = process.env.PARSE_SERVER_LOG_LEVEL; } // Default server configuration for tests. -const defaultConfiguration = { +let defaultConfiguration = { filesAdapter, serverURL: 'http://localhost:' + port + '/1', databaseAdapter, @@ -124,6 +124,8 @@ if (process.env.PARSE_SERVER_TEST_CACHE === 'redis') { defaultConfiguration.cacheAdapter = new RedisCacheAdapter(); } +defaultConfiguration = Object.freeze(defaultConfiguration); + const openConnections = {}; // Set up a default API server for testing with default configuration. let server; From 84581681cf4cbf6c1ea5e227663a0e08defa6618 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 15:40:54 -0600 Subject: [PATCH 08/30] Update Config.js --- src/Config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Config.js b/src/Config.js index a69ce24ff2..521f354d8e 100644 --- a/src/Config.js +++ b/src/Config.js @@ -29,8 +29,7 @@ export class Config { static get(applicationId: string, mount: string) { const cacheInfo = AppCache.get(applicationId); if (!cacheInfo) { - console.trace(); - throw '---------------Empty Cache-------------------'; + return; } const config = new Config(); config.applicationId = applicationId; From 6085eecd3241cad36c8823e1906fd51e382893ac Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 16:15:19 -0600 Subject: [PATCH 09/30] fast-fail false --- .github/workflows/ci.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a159401fa..2dcff18263 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ env: jobs: check-ci: name: CI Self-Check - timeout-minutes: 30 + timeout-minutes: 15 runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 @@ -34,7 +34,7 @@ jobs: run: npm run ci:check check-lint: name: Lint - timeout-minutes: 30 + timeout-minutes: 15 runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 @@ -99,7 +99,7 @@ jobs: NODE_VERSION: 15.11.0 fail-fast: false name: ${{ matrix.name }} - timeout-minutes: 30 + timeout-minutes: 15 runs-on: ubuntu-18.04 services: redis: @@ -148,7 +148,7 @@ jobs: POSTGRES_IMAGE: postgis/postgis:13-3.1 fail-fast: false name: ${{ matrix.name }} - timeout-minutes: 30 + timeout-minutes: 15 runs-on: ubuntu-18.04 services: redis: @@ -188,7 +188,4 @@ jobs: - run: npm run coverage env: CI: true - - name: Setup SSH Session - if: ${{ failure() }} - uses: mxschmitt/action-tmate@v3 - run: bash <(curl -s https://codecov.io/bash) From 56c24aedf0bf915841241327e8a81a50756c4c8c Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 19:30:12 -0600 Subject: [PATCH 10/30] Remove usage of AppCache --- spec/ParseHooks.spec.js | 3 +-- spec/UserController.spec.js | 43 ++++++++++++++----------------------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/spec/ParseHooks.spec.js b/spec/ParseHooks.spec.js index 9434b6f10a..e01af58e5e 100644 --- a/spec/ParseHooks.spec.js +++ b/spec/ParseHooks.spec.js @@ -10,7 +10,6 @@ const Config = require('../lib/Config'); const port = 12345; const hookServerURL = 'http://localhost:' + port; -const AppCache = require('../lib/cache').AppCache; describe('Hooks', () => { let server; @@ -383,7 +382,7 @@ describe('Hooks', () => { } const hooksController = new HooksController( Parse.applicationId, - AppCache.get('test').databaseController + Config.get('test').database ); return hooksController.load(); }, diff --git a/spec/UserController.spec.js b/spec/UserController.spec.js index 94846c59bb..f0ad7f1acc 100644 --- a/spec/UserController.spec.js +++ b/spec/UserController.spec.js @@ -1,6 +1,5 @@ const UserController = require('../lib/Controllers/UserController').UserController; const emailAdapter = require('./MockEmailAdapter'); -const AppCache = require('../lib/cache').AppCache; describe('UserController', () => { const user = { @@ -11,55 +10,45 @@ describe('UserController', () => { describe('sendVerificationEmail', () => { describe('parseFrameURL not provided', () => { - it('uses publicServerURL', done => { - AppCache.put( - defaultConfiguration.appId, - Object.assign({}, defaultConfiguration, { - publicServerURL: 'http://www.example.com', - customPages: { - parseFrameURL: undefined, - }, - }) - ); - + it('uses publicServerURL', async done => { + reconfigureServer({ + publicServerURL: 'http://www.example.com', + customPages: { + parseFrameURL: undefined, + }, + }); emailAdapter.sendVerificationEmail = options => { expect(options.link).toEqual( 'http://www.example.com/apps/test/verify_email?token=testToken&username=testUser' ); + emailAdapter.sendVerificationEmail = () => Promise.resolve(); done(); }; - const userController = new UserController(emailAdapter, 'test', { verifyUserEmails: true, }); - userController.sendVerificationEmail(user); }); }); describe('parseFrameURL provided', () => { - it('uses parseFrameURL and includes the destination in the link parameter', done => { - AppCache.put( - defaultConfiguration.appId, - Object.assign({}, defaultConfiguration, { - publicServerURL: 'http://www.example.com', - customPages: { - parseFrameURL: 'http://someother.example.com/handle-parse-iframe', - }, - }) - ); - + it('uses parseFrameURL and includes the destination in the link parameter', async done => { + reconfigureServer({ + publicServerURL: 'http://www.example.com', + customPages: { + parseFrameURL: 'http://someother.example.com/handle-parse-iframe', + }, + }); emailAdapter.sendVerificationEmail = options => { expect(options.link).toEqual( 'http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=testToken&username=testUser' ); + emailAdapter.sendVerificationEmail = () => Promise.resolve(); done(); }; - const userController = new UserController(emailAdapter, 'test', { verifyUserEmails: true, }); - userController.sendVerificationEmail(user); }); }); From 699836a5543d12a27eda90d4afbf38e5a120802c Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 13 Mar 2021 19:52:04 -0600 Subject: [PATCH 11/30] oops --- spec/UserController.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/UserController.spec.js b/spec/UserController.spec.js index f0ad7f1acc..bd1890b6de 100644 --- a/spec/UserController.spec.js +++ b/spec/UserController.spec.js @@ -11,7 +11,7 @@ describe('UserController', () => { describe('sendVerificationEmail', () => { describe('parseFrameURL not provided', () => { it('uses publicServerURL', async done => { - reconfigureServer({ + await reconfigureServer({ publicServerURL: 'http://www.example.com', customPages: { parseFrameURL: undefined, @@ -33,7 +33,7 @@ describe('UserController', () => { describe('parseFrameURL provided', () => { it('uses parseFrameURL and includes the destination in the link parameter', async done => { - reconfigureServer({ + await reconfigureServer({ publicServerURL: 'http://www.example.com', customPages: { parseFrameURL: 'http://someother.example.com/handle-parse-iframe', From 923376b59fa40aa35b0c0912c54619bddff13f44 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 11:50:39 -0500 Subject: [PATCH 12/30] Update contributing guide --- CONTRIBUTING.md | 8 ++++++++ spec/helper.js | 14 ++++++-------- src/PromiseRouter.js | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd2955430f..043100d8fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,6 +84,14 @@ Once you have babel running in watch mode, you can start making changes to parse * All the tests should point to sources in the `lib/` folder. * The `lib/` folder is produced by `babel` using either the `npm run build`, `npm run watch`, or the `npm run prepare` step. * The `npm run prepare` step is automatically invoked when your package depends on forked parse-server installed via git for example using `npm install --save git+https://github.com/[username]/parse-server#[branch/commit]`. +* The tests are run against a single server instance. You can change the server configurations using `await reconfigureServer({ ... some configuration })` found in `spec/helper.js`. +* The tests are ran at random. +* Caches and Configurations are reset after every test. +* Users are logged out after every test. +* Cloud Code hooks are removed after every test. +* Database is deleted after every test (indexes are not removed for speed) +* Tests are located in the `spec` folder +* For better test reporting enable `PARSE_SERVER_LOG_LEVEL=debug` ### Troubleshooting diff --git a/spec/helper.js b/spec/helper.js index 2b03f31d3d..15577607de 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -5,10 +5,10 @@ const CurrentSpecReporter = require('./support/CurrentSpecReporter.js'); // Sets up a Parse API server for testing. 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()); -// } +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) { @@ -83,7 +83,7 @@ if (process.env.PARSE_SERVER_LOG_LEVEL) { logLevel = process.env.PARSE_SERVER_LOG_LEVEL; } // Default server configuration for tests. -let defaultConfiguration = { +const defaultConfiguration = { filesAdapter, serverURL: 'http://localhost:' + port + '/1', databaseAdapter, @@ -124,8 +124,6 @@ if (process.env.PARSE_SERVER_TEST_CACHE === 'redis') { defaultConfiguration.cacheAdapter = new RedisCacheAdapter(); } -defaultConfiguration = Object.freeze(defaultConfiguration); - const openConnections = {}; // Set up a default API server for testing with default configuration. let server; @@ -149,7 +147,7 @@ const reconfigureServer = (changedConfiguration = {}) => { if (error) { reject(error); } else { - Parse.CoreManager.set('REQUEST_ATTEMPT_LIMIT', 3); + Parse.CoreManager.set('REQUEST_ATTEMPT_LIMIT', 1); resolve(parseServer); } }, diff --git a/src/PromiseRouter.js b/src/PromiseRouter.js index fd5ffb0372..1f531025a9 100644 --- a/src/PromiseRouter.js +++ b/src/PromiseRouter.js @@ -214,7 +214,7 @@ function maskSensitiveUrl(req) { } function clearSchemaCache(req) { - if (req && req.config && req.config.database && !req.config.enableSingleSchemaCache) { + if (req.config && !req.config.enableSingleSchemaCache) { req.config.database.schemaCache.clear(); } } From f95a19383722ae3cb5823c2cc4c175ab2d8314d5 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 12:06:44 -0500 Subject: [PATCH 13/30] enable debugger, try network retry attempt 1 --- spec/helper.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/helper.js b/spec/helper.js index 15577607de..c4e8e13298 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -5,10 +5,10 @@ const CurrentSpecReporter = require('./support/CurrentSpecReporter.js'); // Sets up a Parse API server for testing. 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()); -} +// 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) { From 045bb2845be997498fabae1e9a7866da1577dbeb Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 13:33:49 -0500 Subject: [PATCH 14/30] Fix ldap unbinding --- CONTRIBUTING.md | 1 + spec/LdapAuth.spec.js | 385 ++++++++---------- spec/ParseGraphQLServer.spec.js | 2 +- spec/ParseHooks.spec.js | 4 +- spec/ProtectedFields.spec.js | 2 +- spec/UserController.spec.js | 2 +- spec/ValidationAndPasswordsReset.spec.js | 2 +- spec/VerifyUserPassword.spec.js | 2 +- spec/index.spec.js | 2 +- spec/{ => support}/MockAdapter.js | 0 spec/{ => support}/MockEmailAdapter.js | 0 .../MockEmailAdapterWithOptions.js | 0 spec/{ => support}/MockLdapServer.js | 4 +- spec/{ => support}/MockPushAdapter.js | 0 spec/{ => support}/dev.js | 2 +- spec/{ => support}/myoauth.js | 0 src/Adapters/Auth/ldap.js | 2 + 17 files changed, 182 insertions(+), 228 deletions(-) rename spec/{ => support}/MockAdapter.js (100%) rename spec/{ => support}/MockEmailAdapter.js (100%) rename spec/{ => support}/MockEmailAdapterWithOptions.js (100%) rename spec/{ => support}/MockLdapServer.js (90%) rename spec/{ => support}/MockPushAdapter.js (100%) rename spec/{ => support}/dev.js (98%) rename spec/{ => support}/myoauth.js (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 043100d8fe..c2254e4cc9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,6 +116,7 @@ Once you have babel running in watch mode, you can start making changes to parse * Run the tests for the whole project to make sure the code passes all tests. This can be done by running the test command for a single file but removing the test file argument. The results can be seen at */coverage/lcov-report/index.html*. * Lint your code by running `npm run lint` to make sure the code is not going to be rejected by the CI. * **Do not** publish the *lib* folder. +* Mocks belong in the `spec/support` folder. * Please consider if any changes to the [docs](http://docs.parseplatform.org) are needed or add additional sections in the case of an enhancement or feature. ### Test against Postgres diff --git a/spec/LdapAuth.spec.js b/spec/LdapAuth.spec.js index 09532b217b..ea30f59f0c 100644 --- a/spec/LdapAuth.spec.js +++ b/spec/LdapAuth.spec.js @@ -1,5 +1,5 @@ const ldap = require('../lib/Adapters/Auth/ldap'); -const mockLdapServer = require('./MockLdapServer'); +const mockLdapServer = require('./support/MockLdapServer'); const fs = require('fs'); const port = 12345; const sslport = 12346; @@ -19,243 +19,194 @@ describe('Ldap Auth', () => { ldap.validateAppId().then(done).catch(done.fail); }); - 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 succeed with right credentials', async done => { + const server = await mockLdapServer(port, 'uid=testuser, o=example'); + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + }; + await ldap.validateAuthData({ id: 'testuser', password: 'secret' }, options); + server.close(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 succeed with right credentials when LDAPS is used and certifcate is not checked', async done => { + const server = await mockLdapServer(sslport, 'uid=testuser, o=example', false, true); + const options = { + suffix: 'o=example', + url: `ldaps://localhost:${sslport}`, + dn: 'uid={{id}}, o=example', + tlsOptions: { rejectUnauthorized: false }, + }; + await ldap.validateAuthData({ id: 'testuser', password: 'secret' }, options); + server.close(done); }); - 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 when LDAPS is used and the presented certificate is the expected certificate', async done => { + const server = await mockLdapServer(sslport, 'uid=testuser, o=example', false, true); + 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, + }, + }; + await ldap.validateAuthData({ id: 'testuser', password: 'secret' }, options); + server.close(done); }); - 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 fail when LDAPS is used and the presented certificate is not the expected certificate', async done => { + const server = await mockLdapServer(sslport, 'uid=testuser, o=example', false, true); + 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, + }, + }; + try { + await ldap.validateAuthData({ id: 'testuser', password: 'secret' }, options); + fail(); + } catch (err) { + expect(err.message).toBe('LDAPS: Certificate mismatch'); + } + server.close(done); }); - 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 fail when LDAPS is used certifcate matches but credentials are wrong', async done => { + const server = await mockLdapServer(sslport, 'uid=testuser, o=example', false, true); + 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, + }, + }; + try { + await ldap.validateAuthData({ id: 'testuser', password: 'wrong!' }, options); + fail(); + } catch (err) { + expect(err.message).toBe('LDAP: Wrong username or password'); + } + server.close(done); }); - 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 with wrong credentials', async done => { + const server = await mockLdapServer(port, 'uid=testuser, o=example'); + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + }; + try { + await ldap.validateAuthData({ id: 'testuser', password: 'wrong!' }, options); + fail(); + } catch (err) { + expect(err.message).toBe('LDAP: Wrong username or password'); + } + server.close(done); }); - 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) - .catch(done.fail) - .finally(() => server.close()); - }); + it('Should succeed if user is in given group', async done => { + const server = await mockLdapServer(port, 'uid=testuser, o=example'); + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + groupCn: 'powerusers', + groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', + }; + await ldap.validateAuthData({ id: 'testuser', password: 'secret' }, options); + server.close(done); }); - 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: User not in group'); - done(); - }) - .finally(() => server.close()); - }); + it('Should fail if user is not in given group', async done => { + const server = await mockLdapServer(port, 'uid=testuser, o=example'); + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + groupCn: 'groupTheUserIsNotIn', + groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', + }; + try { + await ldap.validateAuthData({ id: 'testuser', password: 'secret' }, options); + fail(); + } catch (err) { + expect(err.message).toBe('LDAP: User not in group'); + } + server.close(done); }); - 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()); - }); + it('Should fail if the LDAP server does not allow searching inside the provided suffix', async done => { + const server = await mockLdapServer(port, 'uid=testuser, o=example'); + const options = { + suffix: 'o=invalid', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + groupCn: 'powerusers', + groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', + }; + try { + await ldap.validateAuthData({ id: 'testuser', password: 'secret' }, options); + fail(); + } catch (err) { + expect(err.message).toBe('LDAP group search failed'); + } + server.close(done); }); - 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))', - }; - - 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 fail if the LDAP server encounters an error while searching', async done => { + const server = await mockLdapServer(port, 'uid=testuser, o=example', true); + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + groupCn: 'powerusers', + groupFilter: '(&(uniqueMember=uid={{id}}, o=example)(objectClass=groupOfUniqueNames))', + }; + try { + await ldap.validateAuthData({ id: 'testuser', password: 'secret' }, options); + fail(); + } catch (err) { + expect(err.message).toBe('LDAP group search failed'); + } + server.close(done); }); - 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 delete the password from authData after validation', async done => { + const server = await mockLdapServer(port, 'uid=testuser, o=example', true); + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + }; + const authData = { id: 'testuser', password: 'secret' }; + await ldap.validateAuthData(authData, options); + expect(authData).toEqual({ id: 'testuser' }); + server.close(done); }); - 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', async done => { + const server = await mockLdapServer(port, 'uid=testuser, o=example', true); + const options = { + suffix: 'o=example', + url: `ldap://localhost:${port}`, + dn: 'uid={{id}}, o=example', + }; + await reconfigureServer({ auth: { ldap: options } }); + const authData = { authData: { id: 'testuser', password: 'secret' } }; + const returnedUser = await Parse.User.logInWith('ldap', authData); + const query = new Parse.Query('User'); + const user = await query.equalTo('objectId', returnedUser.id).first({ useMasterKey: true }); + expect(user.get('authData')).toEqual({ ldap: { id: 'testuser' } }); + expect(user.get('authData').ldap.password).toBeUndefined(); + server.close(done); }); }); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index ec26f8ad9e..7afd8a549d 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -5,7 +5,7 @@ const fetch = require('node-fetch'); const FormData = require('form-data'); const ws = require('ws'); require('./helper'); -const { updateCLP } = require('./dev'); +const { updateCLP } = require('./support/dev'); const pluralize = require('pluralize'); const { getMainDefinition } = require('apollo-utilities'); diff --git a/spec/ParseHooks.spec.js b/spec/ParseHooks.spec.js index e01af58e5e..a0f354f8ca 100644 --- a/spec/ParseHooks.spec.js +++ b/spec/ParseHooks.spec.js @@ -8,7 +8,7 @@ const bodyParser = require('body-parser'); const auth = require('../lib/Auth'); const Config = require('../lib/Config'); -const port = 12345; +const port = 34567; const hookServerURL = 'http://localhost:' + port; describe('Hooks', () => { @@ -18,7 +18,7 @@ describe('Hooks', () => { if (!app) { app = express(); app.use(bodyParser.json({ type: '*/*' })); - server = app.listen(12345, undefined, done); + server = app.listen(port, undefined, done); } else { done(); } diff --git a/spec/ProtectedFields.spec.js b/spec/ProtectedFields.spec.js index 1bdc3177f1..c3374923db 100644 --- a/spec/ProtectedFields.spec.js +++ b/spec/ProtectedFields.spec.js @@ -1,7 +1,7 @@ const Config = require('../lib/Config'); const Parse = require('parse/node'); const request = require('../lib/request'); -const { className, createRole, createUser, logIn, updateCLP } = require('./dev'); +const { className, createRole, createUser, logIn, updateCLP } = require('./support/dev'); describe('ProtectedFields', function () { it('should handle and empty protectedFields', async function () { diff --git a/spec/UserController.spec.js b/spec/UserController.spec.js index bd1890b6de..6bcc454baf 100644 --- a/spec/UserController.spec.js +++ b/spec/UserController.spec.js @@ -1,5 +1,5 @@ const UserController = require('../lib/Controllers/UserController').UserController; -const emailAdapter = require('./MockEmailAdapter'); +const emailAdapter = require('./support/MockEmailAdapter'); describe('UserController', () => { const user = { diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index 94d9793a39..d7cc72c7b0 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -1,6 +1,6 @@ 'use strict'; -const MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions'); +const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions'); const request = require('../lib/request'); const Config = require('../lib/Config'); diff --git a/spec/VerifyUserPassword.spec.js b/spec/VerifyUserPassword.spec.js index e77657b8e9..ad4571226e 100644 --- a/spec/VerifyUserPassword.spec.js +++ b/spec/VerifyUserPassword.spec.js @@ -1,7 +1,7 @@ 'use strict'; const request = require('../lib/request'); -const MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions'); +const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions'); const verifyPassword = function (login, password, isEmail = false) { const body = !isEmail ? { username: login, password } : { email: login, password }; diff --git a/spec/index.spec.js b/spec/index.spec.js index 65636ee13d..fb0ee821c7 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -1,7 +1,7 @@ 'use strict'; const request = require('../lib/request'); const parseServerPackage = require('../package.json'); -const MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions'); +const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions'); const ParseServer = require('../lib/index'); const Config = require('../lib/Config'); const express = require('express'); diff --git a/spec/MockAdapter.js b/spec/support/MockAdapter.js similarity index 100% rename from spec/MockAdapter.js rename to spec/support/MockAdapter.js diff --git a/spec/MockEmailAdapter.js b/spec/support/MockEmailAdapter.js similarity index 100% rename from spec/MockEmailAdapter.js rename to spec/support/MockEmailAdapter.js diff --git a/spec/MockEmailAdapterWithOptions.js b/spec/support/MockEmailAdapterWithOptions.js similarity index 100% rename from spec/MockEmailAdapterWithOptions.js rename to spec/support/MockEmailAdapterWithOptions.js diff --git a/spec/MockLdapServer.js b/spec/support/MockLdapServer.js similarity index 90% rename from spec/MockLdapServer.js rename to spec/support/MockLdapServer.js index c621ee7f1e..badc28fbab 100644 --- a/spec/MockLdapServer.js +++ b/spec/support/MockLdapServer.js @@ -2,8 +2,8 @@ const ldapjs = require('ldapjs'); const fs = require('fs'); const tlsOptions = { - key: fs.readFileSync(__dirname + '/support/cert/key.pem'), - certificate: fs.readFileSync(__dirname + '/support/cert/cert.pem'), + key: fs.readFileSync(__dirname + '/cert/key.pem'), + certificate: fs.readFileSync(__dirname + '/cert/cert.pem'), }; function newServer(port, dn, provokeSearchError = false, ssl = false) { diff --git a/spec/MockPushAdapter.js b/spec/support/MockPushAdapter.js similarity index 100% rename from spec/MockPushAdapter.js rename to spec/support/MockPushAdapter.js diff --git a/spec/dev.js b/spec/support/dev.js similarity index 98% rename from spec/dev.js rename to spec/support/dev.js index c58879a533..93f73aa5a5 100644 --- a/spec/dev.js +++ b/spec/support/dev.js @@ -1,4 +1,4 @@ -const Config = require('../lib/Config'); +const Config = require('../../lib/Config'); const Parse = require('parse/node'); const className = 'AnObject'; diff --git a/spec/myoauth.js b/spec/support/myoauth.js similarity index 100% rename from spec/myoauth.js rename to spec/support/myoauth.js diff --git a/src/Adapters/Auth/ldap.js b/src/Adapters/Auth/ldap.js index 38a64927ff..7cea9e3f2b 100644 --- a/src/Adapters/Auth/ldap.js +++ b/src/Adapters/Auth/ldap.js @@ -95,6 +95,8 @@ function searchForGroup(client, options, id, resolve, reject) { } }); res.on('error', () => { + client.unbind(); + client.destroy(); return reject(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'LDAP group search failed')); }); }); From 8ef212084562e1f718e416727e124ef798e7d27e Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 13:55:09 -0500 Subject: [PATCH 15/30] move non specs to support --- spec/AdapterLoader.spec.js | 4 ++-- spec/helper.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/AdapterLoader.spec.js b/spec/AdapterLoader.spec.js index af23c146f6..93415c62bf 100644 --- a/spec/AdapterLoader.spec.js +++ b/spec/AdapterLoader.spec.js @@ -6,7 +6,7 @@ const Config = require('../lib/Config'); describe('AdapterLoader', () => { it('should instantiate an adapter from string in object', done => { - const adapterPath = require('path').resolve('./spec/MockAdapter'); + const adapterPath = require('path').resolve('./spec/support/MockAdapter'); const adapter = loadAdapter({ adapter: adapterPath, @@ -119,7 +119,7 @@ describe('AdapterLoader', () => { }); it('should load custom push adapter from string (#3544)', done => { - const adapterPath = require('path').resolve('./spec/MockPushAdapter'); + const adapterPath = require('path').resolve('./spec/support/MockPushAdapter'); const options = { ios: { bundleId: 'bundle.id', diff --git a/spec/helper.js b/spec/helper.js index c4e8e13298..586c835385 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -114,7 +114,7 @@ const defaultConfiguration = { custom: mockCustom(), facebook: mockFacebook(), myoauth: { - module: path.resolve(__dirname, 'myoauth'), // relative path as it's run from src + module: path.resolve(__dirname, 'support/myoauth'), // relative path as it's run from src }, shortLivedAuth: mockShortLivedAuth(), }, From 1f7bee375043b9203008208eb0c996373f71add5 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 14:17:59 -0500 Subject: [PATCH 16/30] add missing mock adapter --- spec/AdapterLoader.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/AdapterLoader.spec.js b/spec/AdapterLoader.spec.js index 93415c62bf..b02e07e708 100644 --- a/spec/AdapterLoader.spec.js +++ b/spec/AdapterLoader.spec.js @@ -23,7 +23,7 @@ describe('AdapterLoader', () => { }); it('should instantiate an adapter from string', done => { - const adapterPath = require('path').resolve('./spec/MockAdapter'); + const adapterPath = require('path').resolve('./spec/support/MockAdapter'); const adapter = loadAdapter(adapterPath); expect(adapter instanceof Object).toBe(true); From 5c0d6976f1e9b06b6512a2a96a293d4480545737 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 14:42:42 -0500 Subject: [PATCH 17/30] fix Parse.Push --- spec/Parse.Push.spec.js | 9 +++++++-- spec/PushController.spec.js | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/spec/Parse.Push.spec.js b/spec/Parse.Push.spec.js index df2b002862..1732e426e3 100644 --- a/spec/Parse.Push.spec.js +++ b/spec/Parse.Push.spec.js @@ -2,10 +2,15 @@ const request = require('../lib/request'); +const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); + const pushCompleted = async pushId => { - let result = await Parse.Push.getPushStatus(pushId); + const query = new Parse.Query('_PushStatus'); + query.equalTo('objectId', pushId); + let result = await query.first({ useMasterKey: true }); while (!(result && result.get('status') === 'succeeded')) { - result = await Parse.Push.getPushStatus(pushId); + await sleep(100); + result = await query.first({ useMasterKey: true }); } }; diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 775ed7cb5b..4626b5e0a7 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -26,10 +26,15 @@ const successfulIOS = function (body, installations) { return Promise.all(promises); }; +const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); + const pushCompleted = async pushId => { - let result = await Parse.Push.getPushStatus(pushId); + const query = new Parse.Query('_PushStatus'); + query.equalTo('objectId', pushId); + let result = await query.first({ useMasterKey: true }); while (!(result && result.get('status') === 'succeeded')) { - result = await Parse.Push.getPushStatus(pushId); + await sleep(100); + result = await query.first({ useMasterKey: true }); } }; From cbea7d03b4d8cd5be4f39553cc8ba7648b692c49 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 14:48:42 -0500 Subject: [PATCH 18/30] RestController should match batch.spec.js --- spec/ParseServerRESTController.spec.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 2a84edaa4e..7590fc34e0 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -169,20 +169,17 @@ describe('ParseServerRESTController', () => { process.env.PARSE_SERVER_TEST_DB === 'postgres' ) { describe('transactions', () => { - 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' ) { - if (!parseServer) { - parseServer = await reconfigureServer({ - databaseAdapter: undefined, - databaseURI: - 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', - }); - } + await reconfigureServer({ + databaseAdapter: undefined, + databaseURI: + 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', + }); await TestUtils.destroyAllDataPermanently(true); } }); From d211b398a97210465a91489ca2b8fe7ab8673847 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 15:49:49 -0500 Subject: [PATCH 19/30] Remove request attempt limit --- spec/helper.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spec/helper.js b/spec/helper.js index 586c835385..e7977bddf7 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -1,14 +1,12 @@ 'use strict'; const semver = require('semver'); const CurrentSpecReporter = require('./support/CurrentSpecReporter.js'); +const { SpecReporter } = require('jasmine-spec-reporter'); // Sets up a Parse API server for testing. 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()); -// } global.on_db = (db, callback, elseCallback) => { if (process.env.PARSE_SERVER_TEST_DB == db) { @@ -147,7 +145,6 @@ const reconfigureServer = (changedConfiguration = {}) => { if (error) { reject(error); } else { - Parse.CoreManager.set('REQUEST_ATTEMPT_LIMIT', 1); resolve(parseServer); } }, From cfd1eff9376ec5b0f147ed9ff5d1f204a80204a2 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 16:49:30 -0500 Subject: [PATCH 20/30] handle index.spec.js --- spec/ParseServerRESTController.spec.js | 3 ++- spec/PostgresInitOptions.spec.js | 6 +++--- spec/batch.spec.js | 4 ++-- spec/helper.js | 13 ++++++++++++- spec/index.spec.js | 20 ++++++++++++++++---- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 7590fc34e0..cfff31f21f 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -181,6 +181,8 @@ describe('ParseServerRESTController', () => { 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', }); await TestUtils.destroyAllDataPermanently(true); + } else { + await reconfigureServer(); } }); @@ -347,7 +349,6 @@ 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/PostgresInitOptions.spec.js b/spec/PostgresInitOptions.spec.js index 73f040701f..069d5a6437 100644 --- a/spec/PostgresInitOptions.spec.js +++ b/spec/PostgresInitOptions.spec.js @@ -28,7 +28,7 @@ function createParseServer(options) { return new Promise((resolve, reject) => { const parseServer = new ParseServer.default( Object.assign({}, defaultConfiguration, options, { - serverURL: 'http://localhost:12666/parse', + serverURL: 'http://localhost:12668/parse', serverStartComplete: error => { if (error) { reject(error); @@ -37,8 +37,8 @@ function createParseServer(options) { const app = express(); app.use('/parse', parseServer.app); - const server = app.listen(12666); - Parse.serverURL = 'http://localhost:12666/parse'; + const server = app.listen(12668); + Parse.serverURL = 'http://localhost:12668/parse'; resolve(server); } }, diff --git a/spec/batch.spec.js b/spec/batch.spec.js index a1041e4ccc..effb7cddb0 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -186,11 +186,12 @@ describe('batch', () => { 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', }); await TestUtils.destroyAllDataPermanently(true); + } else { + 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 myObject .save() @@ -362,7 +363,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(); diff --git a/spec/helper.js b/spec/helper.js index e7977bddf7..8ee874a93f 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -123,6 +123,16 @@ if (process.env.PARSE_SERVER_TEST_CACHE === 'redis') { } const openConnections = {}; +const destroyAliveConnections = function () { + for (const socketId in openConnections) { + try { + openConnections[socketId].destroy(); + delete openConnections[socketId]; + } catch (e) { + /* */ + } + } +}; // Set up a default API server for testing with default configuration. let server; @@ -192,8 +202,9 @@ beforeAll(async () => { afterEach(function (done) { const afterLogOut = async () => { if (Object.keys(openConnections).length > 0) { - fail('There were open connections to the server left after the test finished'); + console.warn('There were open connections to the server left after the test finished'); } + destroyAliveConnections(); await TestUtils.destroyAllDataPermanently(true); if (didChangeConfiguration) { await reconfigureServer(); diff --git a/spec/index.spec.js b/spec/index.spec.js index fb0ee821c7..73be17cb51 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -317,10 +317,16 @@ describe('server', () => { }) .then(obj => { expect(obj.id).toEqual(objId); - server.close(done); + server.close(async () => { + await reconfigureServer(); + done(); + }); }) .catch(() => { - server.close(done); + server.close(async () => { + await reconfigureServer(); + done(); + }); }); }, }) @@ -354,12 +360,18 @@ describe('server', () => { }) .then(obj => { expect(obj.id).toEqual(objId); - server.close(done); + server.close(async () => { + await reconfigureServer(); + done(); + }); }) .catch(error => { fail(JSON.stringify(error)); if (server) { - server.close(done); + server.close(async () => { + await reconfigureServer(); + done(); + }); } else { done(); } From 877f3660e5933629fa2fa2d4c8ea65da4b80472f Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 17:13:00 -0500 Subject: [PATCH 21/30] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ spec/ParseServerRESTController.spec.js | 1 + spec/batch.spec.js | 1 + 3 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54d4010789..c462fc24fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,8 @@ ___ - Allow Cloud Validator `options` to be async (dblythy) [#7155](https://github.com/parse-community/parse-server/pull/7155) - Optimize queries on classes with pointer permissions (Pedro Diaz) [#7061](https://github.com/parse-community/parse-server/pull/7061) - Test Parse Server continuously against all relevant Postgres versions (minor versions), added Postgres compatibility table to Parse Server docs (Corey Baker) [#7176](https://github.com/parse-community/parse-server/pull/7176) +- Randomize test suite (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265) +- LDAP: Properly unbind client on group search error (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265) ___ ## 4.5.0 [Full Changelog](https://github.com/parse-community/parse-server/compare/4.4.0...4.5.0) diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index cfff31f21f..24e3c5bcbc 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -349,6 +349,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/batch.spec.js b/spec/batch.spec.js index effb7cddb0..412dac6a61 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -363,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 d3883b3fa04763bc90e51e5502d98b7571cb8512 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 17:54:14 -0500 Subject: [PATCH 22/30] Handle error: tuple concurrently updated --- spec/batch.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 412dac6a61..abf6152c69 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -352,7 +352,7 @@ describe('batch', () => { transaction: true, }), }).catch(error => { - expect(error.data).toBeDefined(); + expect(error).toBeDefined(); const query = new Parse.Query('MyObject'); query.find().then(results => { expect(results.length).toBe(0); From 6d3c27631fbff50573b6b1418409719dc39b8da3 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 18:25:40 -0500 Subject: [PATCH 23/30] test transactions --- spec/ParseServerRESTController.spec.js | 17 +++++++++++------ spec/batch.spec.js | 17 +++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 24e3c5bcbc..a8405c3217 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -337,14 +337,19 @@ describe('ParseServerRESTController', () => { }, ], transaction: true, - }).catch(error => { - expect(error).toBeDefined(); - const query = new Parse.Query('MyObject'); - query.find().then(results => { - expect(results.length).toBe(0); + }) + .then(response => { + console.log(response); done(); + }) + .catch(error => { + expect(error).toBeDefined(); + const query = new Parse.Query('MyObject'); + query.find().then(results => { + expect(results.length).toBe(0); + done(); + }); }); - }); }); }); diff --git a/spec/batch.spec.js b/spec/batch.spec.js index abf6152c69..84558cd3da 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -351,14 +351,19 @@ describe('batch', () => { ], transaction: true, }), - }).catch(error => { - expect(error).toBeDefined(); - const query = new Parse.Query('MyObject'); - query.find().then(results => { - expect(results.length).toBe(0); + }) + .then(response => { + console.log(response); done(); + }) + .catch(error => { + expect(error).toBeDefined(); + const query = new Parse.Query('MyObject'); + query.find().then(results => { + expect(results.length).toBe(0); + done(); + }); }); - }); }); }); From 278aa59572f3726bf6fb5fa3e308bdc6959c496b Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 19:20:52 -0500 Subject: [PATCH 24/30] Clear RedisCache after every test --- spec/RedisCacheAdapter.spec.js | 37 +++++++++++++++------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/spec/RedisCacheAdapter.spec.js b/spec/RedisCacheAdapter.spec.js index ac5c209c82..c980e85b7e 100644 --- a/spec/RedisCacheAdapter.spec.js +++ b/spec/RedisCacheAdapter.spec.js @@ -1,6 +1,11 @@ const RedisCacheAdapter = require('../lib/Adapters/Cache/RedisCacheAdapter').default; const Config = require('../lib/Config'); +function wait(sleep) { + return new Promise(function (resolve) { + setTimeout(resolve, sleep); + }); +} /* To run this test part of the complete suite set PARSE_SERVER_TEST_CACHE='redis' @@ -11,31 +16,30 @@ describe_only(() => { })('RedisCacheAdapter', function () { const KEY = 'hello'; const VALUE = 'world'; + let cache; - function wait(sleep) { - return new Promise(function (resolve) { - setTimeout(resolve, sleep); - }); - } + beforeEach(async () => { + cache = new RedisCacheAdapter(null, 100); + await cache.clear(); + }); it('should get/set/clear', done => { - const cache = new RedisCacheAdapter({ + const cacheNaN = new RedisCacheAdapter({ ttl: NaN, }); - cache + cacheNaN .put(KEY, VALUE) - .then(() => cache.get(KEY)) + .then(() => cacheNaN.get(KEY)) .then(value => expect(value).toEqual(VALUE)) - .then(() => cache.clear()) - .then(() => cache.get(KEY)) + .then(() => cacheNaN.clear()) + .then(() => cacheNaN.get(KEY)) .then(value => expect(value).toEqual(null)) + .then(() => cacheNaN.clear()) .then(done); }); it('should expire after ttl', done => { - const cache = new RedisCacheAdapter(null, 100); - cache .put(KEY, VALUE) .then(() => cache.get(KEY)) @@ -47,8 +51,6 @@ describe_only(() => { }); it('should not store value for ttl=0', done => { - const cache = new RedisCacheAdapter(null, 100); - cache .put(KEY, VALUE, 0) .then(() => cache.get(KEY)) @@ -57,8 +59,6 @@ describe_only(() => { }); it('should not expire when ttl=Infinity', done => { - const cache = new RedisCacheAdapter(null, 100); - cache .put(KEY, VALUE, Infinity) .then(() => cache.get(KEY)) @@ -70,7 +70,6 @@ describe_only(() => { }); it('should fallback to default ttl', done => { - const cache = new RedisCacheAdapter(null, 100); let promise = Promise.resolve(); [-100, null, undefined, 'not number', true].forEach(ttl => { @@ -89,8 +88,6 @@ describe_only(() => { }); it('should find un-expired records', done => { - const cache = new RedisCacheAdapter(null, 100); - cache .put(KEY, VALUE) .then(() => cache.get(KEY)) @@ -102,8 +99,6 @@ describe_only(() => { }); it('handleShutdown, close connection', async () => { - const cache = new RedisCacheAdapter(null, 100); - await cache.handleShutdown(); setTimeout(() => { expect(cache.client.connected).toBe(false); From 56bcdb605b16e9af5c08e3aec57059f746d42a1b Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 20:15:03 -0500 Subject: [PATCH 25/30] LoggerController.spec.js --- spec/LoggerController.spec.js | 2 ++ src/Adapters/Cache/RedisCacheAdapter/index.js | 13 +++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/spec/LoggerController.spec.js b/spec/LoggerController.spec.js index 7b2e786789..37d477444c 100644 --- a/spec/LoggerController.spec.js +++ b/spec/LoggerController.spec.js @@ -70,6 +70,7 @@ describe('LoggerController', () => { }; const loggerController = new LoggerController(new WinstonLoggerAdapter()); + loggerController.error('can process an ascending query without throwing'); expect(() => { loggerController @@ -115,6 +116,7 @@ describe('LoggerController', () => { }; const loggerController = new LoggerController(new WinstonLoggerAdapter()); + loggerController.error('can process a descending query without throwing'); expect(() => { loggerController diff --git a/src/Adapters/Cache/RedisCacheAdapter/index.js b/src/Adapters/Cache/RedisCacheAdapter/index.js index 17662628af..dfe89e1621 100644 --- a/src/Adapters/Cache/RedisCacheAdapter/index.js +++ b/src/Adapters/Cache/RedisCacheAdapter/index.js @@ -5,8 +5,9 @@ import { KeyPromiseQueue } from './KeyPromiseQueue'; const DEFAULT_REDIS_TTL = 30 * 1000; // 30 seconds in milliseconds const FLUSH_DB_KEY = '__flush_db__'; -function debug() { - logger.debug.apply(logger, ['RedisCacheAdapter', ...arguments]); +function debug(...args: any) { + const message = ['RedisCacheAdapter: ' + arguments[0]].concat(args.slice(1, args.length)); + logger.debug.apply(logger, message); } const isValidTTL = ttl => typeof ttl === 'number' && ttl > 0; @@ -33,13 +34,13 @@ export class RedisCacheAdapter { } get(key) { - debug('get', key); + debug('get', { key }); return this.queue.enqueue( key, () => new Promise(resolve => { this.client.get(key, function (err, res) { - debug('-> get', key, res); + debug('-> get', { key, res }); if (!res) { return resolve(null); } @@ -51,7 +52,7 @@ export class RedisCacheAdapter { put(key, value, ttl = this.ttl) { value = JSON.stringify(value); - debug('put', key, value, ttl); + debug('put', { key, value, ttl }); if (ttl === 0) { // ttl of zero is a logical no-op, but redis cannot set expire time of zero @@ -86,7 +87,7 @@ export class RedisCacheAdapter { } del(key) { - debug('del', key); + debug('del', { key }); return this.queue.enqueue( key, () => From cc20f6de9029f0cc2056266df2a66f659b4df9c6 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 20:26:47 -0500 Subject: [PATCH 26/30] Update schemas.spec.js --- spec/schemas.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index cba50f387f..e535b07a07 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -1274,6 +1274,7 @@ describe('schemas', () => { }, }, }).then(response => { + delete response.data.indexes; expect( dd(response.data, { className: '_User', @@ -1302,6 +1303,7 @@ describe('schemas', () => { headers: masterKeyHeaders, json: true, }).then(response => { + delete response.data.indexes; expect( dd(response.data, { className: '_User', From 869ce52085b3d2aa3509abe94bace94b036958d0 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 21:06:13 -0500 Subject: [PATCH 27/30] finally fix transactions --- spec/ParseServerRESTController.spec.js | 219 ++++++++++++------------ spec/batch.spec.js | 228 ++++++++++++------------- 2 files changed, 214 insertions(+), 233 deletions(-) diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index a8405c3217..2e81d679fc 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -235,122 +235,113 @@ describe('ParseServerRESTController', () => { .catch(done.fail); }); - it('should not save anything when one operation fails in a transaction', done => { + it('should not save anything when one operation fails in a transaction', async () => { const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections - myObject - .save() - .then(() => { - return myObject.destroy(); - }) - .then(() => { - RESTController.request('POST', 'batch', { - requests: [ - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - ], - transaction: true, - }) - .then(response => { - console.log(response); - done(); - }) - .catch(error => { - expect(error).toBeDefined(); - const query = new Parse.Query('MyObject'); - query.find().then(results => { - expect(results.length).toBe(0); - done(); - }); - }); + await myObject.save(); + await myObject.destroy(); + try { + await RESTController.request('POST', 'batch', { + requests: [ + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + ], + transaction: true, }); + fail(); + } catch (error) { + expect(error).toBeDefined(); + const query = new Parse.Query('MyObject'); + const results = await query.find(); + expect(results.length).toBe(0); + } }); it('should generate separate session for each call', async () => { diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 84558cd3da..ea9777b786 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -244,127 +244,117 @@ describe('batch', () => { }); }); - it('should not save anything when one operation fails in a transaction', done => { + it('should not save anything when one operation fails in a transaction', async () => { const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections - myObject - .save() - .then(() => { - return myObject.destroy(); - }) - .then(() => { - request({ - method: 'POST', - headers: headers, - url: 'http://localhost:8378/1/batch', - body: JSON.stringify({ - requests: [ - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 10 }, - }, - ], - transaction: true, - }), - }) - .then(response => { - console.log(response); - done(); - }) - .catch(error => { - expect(error).toBeDefined(); - const query = new Parse.Query('MyObject'); - query.find().then(results => { - expect(results.length).toBe(0); - done(); - }); - }); + await myObject.save(); + await myObject.destroy(); + try { + await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1/batch', + body: JSON.stringify({ + requests: [ + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 10 }, + }, + ], + transaction: true, + }), }); + } catch (error) { + expect(error).toBeDefined(); + const query = new Parse.Query('MyObject'); + const results = await query.find(); + expect(results.length).toBe(0); + } }); it('should generate separate session for each call', async () => { From b6694d66c638db792bcf8eaf1675c3b29698e35e Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 21:42:20 -0500 Subject: [PATCH 28/30] fix geopoint deadlock --- src/Controllers/SchemaController.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index a5e7d2838a..8253746f8a 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -1215,16 +1215,10 @@ export default class SchemaController { const promises = []; for (const fieldName in object) { - if (object[fieldName] === undefined) { - continue; - } - const expected = getType(object[fieldName]); - if (expected === 'GeoPoint') { + if (object[fieldName] && getType(object[fieldName]) === 'GeoPoint') { geocount++; } if (geocount > 1) { - // Make sure all field validation operations run before we return. - // If not - we are continuing to run logic, but already provided response from the server. return Promise.reject( new Parse.Error( Parse.Error.INCORRECT_TYPE, @@ -1232,6 +1226,12 @@ export default class SchemaController { ) ); } + } + for (const fieldName in object) { + if (object[fieldName] === undefined) { + continue; + } + const expected = getType(object[fieldName]); if (!expected) { continue; } From 0893e8e6ba57c5b36bf1fe81a99550a7e113b56c Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 22:32:47 -0500 Subject: [PATCH 29/30] transaction with clean database --- spec/ParseServerRESTController.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 2e81d679fc..81fb71cd46 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -170,6 +170,7 @@ describe('ParseServerRESTController', () => { ) { describe('transactions', () => { beforeEach(async () => { + await TestUtils.destroyAllDataPermanently(true); if ( semver.satisfies(process.env.MONGODB_VERSION, '>=4.0.4') && process.env.MONGODB_TOPOLOGY === 'replicaset' && @@ -180,7 +181,6 @@ describe('ParseServerRESTController', () => { databaseURI: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', }); - await TestUtils.destroyAllDataPermanently(true); } else { await reconfigureServer(); } From 326a0f40838773105a5ee93898dba80bd1a7db6f Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 14 Mar 2021 22:56:02 -0500 Subject: [PATCH 30/30] batch.spec.js --- spec/ParseServerRESTController.spec.js | 3 ++- spec/batch.spec.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 81fb71cd46..90fe383257 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -186,7 +186,8 @@ describe('ParseServerRESTController', () => { } }); - it('should handle a batch request with transaction = true', done => { + 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() diff --git a/spec/batch.spec.js b/spec/batch.spec.js index ea9777b786..98050254ba 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -175,6 +175,7 @@ describe('batch', () => { ) { describe('transactions', () => { beforeEach(async () => { + await TestUtils.destroyAllDataPermanently(true); if ( semver.satisfies(process.env.MONGODB_VERSION, '>=4.0.4') && process.env.MONGODB_TOPOLOGY === 'replicaset' && @@ -185,13 +186,13 @@ describe('batch', () => { databaseURI: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset', }); - await TestUtils.destroyAllDataPermanently(true); } else { 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 myObject .save()