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:
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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' })
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ describe('ParseGraphQLSchema', () => {
|
||||
let parseGraphQLSchema;
|
||||
const appId = 'test';
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
parseServer = await global.reconfigureServer({
|
||||
schemaCacheTTL: 100,
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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: [],
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
15
spec/support/CurrentSpecReporter.js
Executable file
15
spec/support/CurrentSpecReporter.js
Executable 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;
|
||||
Reference in New Issue
Block a user