Allow single server instance in test suite (#7262)

* initial pass

* reconfigureServer when needed

* finish postgres tests

* mongo tests

* more tests

* clean up

* re-add skipped test

* Fix transaction tests

* handle batch

* AuthenticationAdapter fix

* More reconfiguration

* clean up

* properly terminate cli servers

* handle Parse.Push

* Flaky PushController

* ensure reconfigureServer when changed

* fix postgres tests

* remove console.log

* LiveQuery spec remove duplicates and listeners
This commit is contained in:
Diamond Lewis
2021-03-13 09:05:22 -06:00
committed by GitHub
parent 8b0e8cd02c
commit 9563793303
36 changed files with 941 additions and 1020 deletions

View File

@@ -209,8 +209,12 @@ describe('execution', () => {
const binPath = path.resolve(__dirname, '../bin/parse-server');
let childProcess;
afterEach(async () => {
afterEach(done => {
if (childProcess) {
childProcess.on('close', () => {
childProcess = undefined;
done();
});
childProcess.kill();
}
});

View File

@@ -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();
});

View File

@@ -2740,45 +2740,193 @@ 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', () => {
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();
@@ -3023,189 +3171,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');
it('throw empty error from beforeSaveFile', async done => {
Parse.Cloud.beforeSaveFile(() => {
throw null;
});
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' } });
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();
}
});
});
@@ -3215,7 +3217,7 @@ describe('sendEmail', () => {
sendMail: mailData => {
expect(mailData).toBeDefined();
expect(mailData.to).toBe('test');
done();
reconfigureServer().then(done, done);
},
};
await reconfigureServer({

View File

@@ -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')

View File

@@ -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 => {

View File

@@ -4,255 +4,257 @@ 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();
describe('Ldap Auth', () => {
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);
});
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 return a resolved promise when validating the app id', done => {
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 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', 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 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', 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 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', 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 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', 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 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', 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 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 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());
ldap
.validateAuthData({ id: 'testuser', password: 'secret' }, options)
.then(done)
.catch(done.fail)
.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 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());
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 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 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 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 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());
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 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' };
const authData = { id: 'testuser', password: 'secret' };
ldap
.validateAuthData(authData, options)
.then(() => {
expect(authData).toEqual({ id: 'testuser' });
done();
})
.catch(done.fail)
.finally(() => server.close());
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());
});
});
});
});

View File

@@ -32,11 +32,13 @@ 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', done => {

View File

@@ -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);
});

View File

@@ -67,6 +67,7 @@ describe('miscellaneous', function () {
});
it('fail to create a duplicate username', async () => {
await reconfigureServer();
let numFailed = 0;
let numCreated = 0;
const p1 = request({
@@ -114,6 +115,7 @@ describe('miscellaneous', function () {
});
it('ensure that email is uniquely indexed', async () => {
await reconfigureServer();
let numFailed = 0;
let numCreated = 0;
const p1 = request({
@@ -246,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' })

View File

@@ -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',

View File

@@ -28,42 +28,41 @@ describe('ParseGraphQLController', () => {
return graphQLConfigRecord;
};
beforeAll(async () => {
parseServer = await global.reconfigureServer({
schemaCacheTTL: 100,
});
databaseController = parseServer.config.databaseController;
cacheController = parseServer.config.cacheController;
beforeEach(async () => {
if (!parseServer) {
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);
}
};
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);
}
};
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);
}
};
});
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;
});

View File

@@ -9,7 +9,7 @@ describe('ParseGraphQLSchema', () => {
let parseGraphQLSchema;
const appId = 'test';
beforeAll(async () => {
beforeEach(async () => {
parseServer = await global.reconfigureServer({
schemaCacheTTL: 100,
});

View File

@@ -43,7 +43,7 @@ describe('ParseGraphQLServer', () => {
let parseServer;
let parseGraphQLServer;
beforeAll(async () => {
beforeEach(async () => {
parseServer = await global.reconfigureServer({});
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
graphQLPath: '/graphql',
@@ -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();
});
@@ -700,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 () => {
@@ -2175,11 +2176,7 @@ describe('ParseGraphQLServer', () => {
});
describe('Relay Spec', () => {
beforeAll(async () => {
await resetGraphQLCache();
});
afterAll(async () => {
beforeEach(async () => {
await resetGraphQLCache();
});
@@ -10079,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, {
@@ -10112,7 +10109,7 @@ describe('ParseGraphQLServer', () => {
});
});
afterAll(async () => {
afterEach(async () => {
await httpServer.close();
});
@@ -10203,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();
},
},
},
}),
types: [
new GraphQLInputObjectType({
name: 'CreateSomeClassFieldsInput',
fields: {
type: { type: TypeEnum },
},
}),
new GraphQLInputObjectType({
name: 'UpdateSomeClassFieldsInput',
fields: {
type: { type: TypeEnum },
},
}),
SomeClassType,
],
}),
});
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();
},
},
},
}),
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 () => {
@@ -10393,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 () => {

View File

@@ -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 => {

View File

@@ -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();
});

View File

@@ -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,7 +209,9 @@ describe('Parse.Polygon testing', () => {
});
describe('with location', () => {
beforeAll(() => require('../lib/TestUtils').destroyAllDataPermanently());
if (process.env.PARSE_SERVER_TEST_DB !== 'postgres') {
beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently());
}
it('polygonContain query', done => {
const points1 = [
@@ -236,13 +236,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 },
},
@@ -288,13 +288,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 },
},
@@ -326,12 +326,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',
@@ -366,12 +366,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 },
},
@@ -398,12 +398,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: [],
},

View File

@@ -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 });
@@ -1403,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({
@@ -1453,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({
@@ -1500,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);
});
});

View File

@@ -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' });
@@ -933,10 +937,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 +2927,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');

View File

@@ -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()

View File

@@ -10,26 +10,32 @@ 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 => {
Parse.serverURL = 'http://localhost:8378/1';
server.close(done);
});
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');
}
await reconfigureServer();
done();
});
});
@@ -37,10 +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');
}
await reconfigureServer();
done();
});
});
@@ -98,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();
});
});

View File

@@ -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;
@@ -168,17 +169,21 @@ 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) {
parseServer = await reconfigureServer({
databaseAdapter: undefined,
databaseURI:
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset',
});
}
await TestUtils.destroyAllDataPermanently(true);
}
});
@@ -215,7 +220,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]
@@ -346,6 +350,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();

View File

@@ -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_',
@@ -826,8 +827,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 +2909,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 +3900,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 {
@@ -3929,6 +3933,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({
@@ -3962,6 +3967,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');
@@ -4000,6 +4006,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({

View File

@@ -50,9 +50,10 @@ function createParseServer(options) {
describe_only_db('postgres')('Postgres database init options', () => {
let server;
afterEach(() => {
afterAll(done => {
if (server) {
server.close();
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();
});
});
});

View File

@@ -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 = {
@@ -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));
@@ -153,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;
@@ -172,74 +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;
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);
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';
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');
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 [];
//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';
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;
@@ -249,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(
@@ -292,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';
@@ -308,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,

View File

@@ -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) {

View File

@@ -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,8 @@ 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);

View File

@@ -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();

View File

@@ -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';
@@ -88,7 +89,7 @@ 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 => {
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({
@@ -172,7 +174,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' &&
@@ -183,10 +185,12 @@ describe('batch', () => {
databaseURI:
'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset',
});
await TestUtils.destroyAllDataPermanently(true);
}
});
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()
@@ -224,7 +228,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]
@@ -359,6 +362,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();

View File

@@ -1,8 +1,14 @@
'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());
}
global.on_db = (db, callback, elseCallback) => {
if (process.env.PARSE_SERVER_TEST_DB == db) {
@@ -32,6 +38,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';
@@ -120,8 +127,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(() => {
@@ -131,6 +140,7 @@ const reconfigureServer = changedConfiguration => {
}
try {
let parseServer = undefined;
didChangeConfiguration = Object.keys(changedConfiguration).length !== 0;
const newConfiguration = Object.assign({}, defaultConfiguration, changedConfiguration, {
serverStartComplete: error => {
if (error) {
@@ -167,7 +177,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) {
@@ -182,11 +192,17 @@ beforeEach(async () => {
});
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(done, done);
await TestUtils.destroyAllDataPermanently(true);
if (didChangeConfiguration) {
await reconfigureServer();
} else {
await databaseAdapter.performInitialization({ VolatileClassesSchemas });
}
done();
};
Parse.Cloud._removeAllHooks();
databaseAdapter

View File

@@ -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);

View File

@@ -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' },
});
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', done => {
reconfigureServer({
masterKey: 'yolo',
readOnlyMasterKey: 'yolo',
})
.then(done.fail)
.catch(err => {
expect(err).toEqual(new Error('masterKey and readOnlyMasterKey should be different'));
done();
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: {

View File

@@ -140,14 +140,13 @@ const masterKeyHeaders = {
};
describe('schemas', () => {
beforeEach(() => {
beforeEach(async () => {
await reconfigureServer();
config = Config.get('test');
});
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 => {

View File

@@ -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;