Files
kami-parse-server/spec/ParseLiveQuery.spec.js

1234 lines
37 KiB
JavaScript

'use strict';
const Auth = require('../lib/Auth');
const UserController = require('../lib/Controllers/UserController').UserController;
const Config = require('../lib/Config');
const ParseServer = require('../lib/index').ParseServer;
const triggers = require('../lib/triggers');
const { resolvingPromise, sleep } = require('../lib/TestUtils');
const validatorFail = () => {
throw 'you are not authorized';
};
describe('ParseLiveQuery', function () {
beforeEach(() => {
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(null);
});
afterEach(async () => {
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
await client.close();
});
it('access user on onLiveQueryEvent disconnect', async done => {
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();
}
});
await requestedUser.signUp();
const query = new Parse.Query(TestObject);
await query.subscribe();
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
await client.close();
});
it('can subscribe to query', async done => {
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('update', object => {
expect(object.get('foo')).toBe('bar');
done();
});
object.set({ foo: 'bar' });
await object.save();
});
it('can use patterns in className', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['Test.*'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
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('update', object => {
expect(object.get('foo')).toBe('bar');
done();
});
object.set({ foo: 'bar' });
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 => {
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 => {
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 => {
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 => {
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 return different object in afterEvent', 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 object = new Parse.Object('Yolo');
req.object = object;
});
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
subscription.on('update', object => {
expect(object.className).toBe('Yolo');
done();
});
object.set({ foo: 'bar' });
await object.save();
});
it('can handle afterEvent throw', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const object = new TestObject();
await object.save();
Parse.Cloud.afterLiveQueryEvent('TestObject', () => {
throw 'Throw error from LQ afterEvent.';
});
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
subscription.on('update', () => {
fail('update should not have been called.');
});
subscription.on('error', e => {
expect(e).toBe('Throw error from LQ afterEvent.');
done();
});
object.set({ foo: 'bar' });
await object.save();
});
it('can log on afterLiveQueryEvent throw', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const object = new TestObject();
await object.save();
const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});
let session = undefined;
Parse.Cloud.afterLiveQueryEvent('TestObject', ({ sessionToken }) => {
session = sessionToken;
/* eslint-disable no-undef */
foo.bar();
/* eslint-enable no-undef */
});
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
object.set({ foo: 'bar' });
await object.save();
await new Promise(resolve => subscription.on('error', resolve));
expect(logger.error).toHaveBeenCalledWith(
`Failed running afterLiveQueryEvent on class TestObject for event update with session ${session} with:\n Error: {"message":"foo is not defined","code":141}`
);
});
it('can handle afterEvent sendEvent to false', async () => {
const object = new TestObject();
await object.save();
const promise = resolvingPromise();
Parse.Cloud.afterLiveQueryEvent('TestObject', req => {
const current = req.object;
const original = req.original;
if (current.get('foo') != original.get('foo')) {
req.sendEvent = false;
}
promise.resolve();
});
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
subscription.on('update', () => {
fail('update should not have been called.');
});
subscription.on('error', () => {
fail('error should not have been called.');
});
object.set({ foo: 'bar' });
await object.save();
await promise;
});
it('can handle live query with fields', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['Test'],
},
startLiveQueryServer: true,
});
const query = new Parse.Query('Test');
query.watch('yolo');
const subscription = await query.subscribe();
const spy = {
create(obj) {
if (!obj.get('yolo')) {
fail('create should not have been called');
}
},
update(object, original) {
if (object.get('yolo') === original.get('yolo')) {
fail('create should not have been called');
}
},
};
const createSpy = spyOn(spy, 'create').and.callThrough();
const updateSpy = spyOn(spy, 'update').and.callThrough();
subscription.on('create', spy.create);
subscription.on('update', spy.update);
const obj = new Parse.Object('Test');
obj.set('foo', 'bar');
await obj.save();
obj.set('foo', 'xyz');
obj.set('yolo', 'xyz');
await obj.save();
const obj2 = new Parse.Object('Test');
obj2.set('foo', 'bar');
obj2.set('yolo', 'bar');
await obj2.save();
obj2.set('foo', 'bart');
await obj2.save();
expect(createSpy).toHaveBeenCalledTimes(1);
expect(updateSpy).toHaveBeenCalledTimes(1);
});
it('can handle afterEvent set pointers', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const object = new TestObject();
await object.save();
const secondObject = new Parse.Object('Test2');
secondObject.set('foo', 'bar');
await secondObject.save();
Parse.Cloud.afterLiveQueryEvent('TestObject', async ({ object }) => {
const query = new Parse.Query('Test2');
const obj = await query.first();
object.set('obj', obj);
});
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
subscription.on('update', object => {
expect(object.get('obj')).toBeDefined();
expect(object.get('obj').get('foo')).toBe('bar');
done();
});
subscription.on('error', () => {
fail('error should not have been called.');
});
object.set({ foo: 'bar' });
await object.save();
});
it('can handle async afterEvent modification', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const parent = new TestObject();
const child = new TestObject();
child.set('bar', 'foo');
await Parse.Object.saveAll([parent, child]);
Parse.Cloud.afterLiveQueryEvent('TestObject', async req => {
const current = req.object;
const pointer = current.get('child');
await pointer.fetch();
});
const query = new Parse.Query(TestObject);
query.equalTo('objectId', parent.id);
const subscription = await query.subscribe();
subscription.on('update', object => {
expect(object.get('child')).toBeDefined();
expect(object.get('child').get('bar')).toBe('foo');
done();
});
parent.set('child', child);
await parent.save();
});
it('can handle beforeConnect / beforeSubscribe hooks', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
});
const object = new TestObject();
await object.save();
const hooks = {
beforeSubscribe(req) {
expect(req.op).toBe('subscribe');
expect(req.requestId).toBe(1);
expect(req.query).toBeDefined();
expect(req.user).toBeUndefined();
},
beforeConnect(req) {
expect(req.event).toBe('connect');
expect(req.clients).toBe(0);
expect(req.subscriptions).toBe(0);
expect(req.useMasterKey).toBe(false);
expect(req.installationId).toBeDefined();
expect(req.user).toBeUndefined();
expect(req.client).toBeDefined();
},
};
spyOn(hooks, 'beforeSubscribe').and.callThrough();
spyOn(hooks, 'beforeConnect').and.callThrough();
Parse.Cloud.beforeSubscribe('TestObject', hooks.beforeSubscribe);
Parse.Cloud.beforeConnect(hooks.beforeConnect);
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
subscription.on('update', object => {
expect(object.get('foo')).toBe('bar');
expect(hooks.beforeConnect).toHaveBeenCalled();
expect(hooks.beforeSubscribe).toHaveBeenCalled();
done();
});
object.set({ foo: 'bar' });
await object.save();
});
it('can handle beforeConnect validation function', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
});
const object = new TestObject();
await object.save();
Parse.Cloud.beforeConnect(() => {}, validatorFail);
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
await expectAsync(query.subscribe()).toBeRejectedWith(
new Parse.Error(Parse.Error.VALIDATION_ERROR, 'you are not authorized')
);
});
it('can handle beforeSubscribe validation function', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
});
const object = new TestObject();
await object.save();
Parse.Cloud.beforeSubscribe(TestObject, () => {}, validatorFail);
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
await expectAsync(query.subscribe()).toBeRejectedWith(
new Parse.Error(Parse.Error.VALIDATION_ERROR, 'you are not authorized')
);
});
it('can handle afterEvent validation function', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
Parse.Cloud.afterLiveQueryEvent('TestObject', () => {}, validatorFail);
const query = new Parse.Query(TestObject);
const subscription = await query.subscribe();
subscription.on('error', error => {
expect(error).toBe('you are not authorized');
done();
});
const object = new TestObject();
object.set('foo', 'bar');
await object.save();
});
it('can handle beforeConnect error', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
});
const object = new TestObject();
await object.save();
Parse.Cloud.beforeConnect(() => {
throw new Error('You shall not pass!');
});
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
await expectAsync(query.subscribe()).toBeRejectedWith(new Error('You shall not pass!'));
});
it('can log on beforeConnect throw', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
});
const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});
let token = undefined;
Parse.Cloud.beforeConnect(({ sessionToken }) => {
token = sessionToken;
/* eslint-disable no-undef */
foo.bar();
/* eslint-enable no-undef */
});
await expectAsync(new Parse.Query(TestObject).subscribe()).toBeRejectedWith(
new Error('foo is not defined')
);
expect(logger.error).toHaveBeenCalledWith(
`Failed running beforeConnect for session ${token} with:\n Error: {"message":"foo is not defined","code":141}`
);
});
it('can handle beforeSubscribe error', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
});
const object = new TestObject();
await object.save();
Parse.Cloud.beforeSubscribe(TestObject, () => {
throw new Error('You shall not subscribe!');
});
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
await expectAsync(query.subscribe()).toBeRejectedWith(new Error('You shall not subscribe!'));
});
it('can log on beforeSubscribe error', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
});
const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});
Parse.Cloud.beforeSubscribe(TestObject, () => {
/* eslint-disable no-undef */
foo.bar();
/* eslint-enable no-undef */
});
const query = new Parse.Query(TestObject);
await expectAsync(query.subscribe()).toBeRejectedWith(new Error('foo is not defined'));
expect(logger.error).toHaveBeenCalledWith(
`Failed running beforeSubscribe on TestObject for session undefined with:\n Error: {"message":"foo is not defined","code":141}`
);
});
it('can handle mutate beforeSubscribe query', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
});
const hook = {
beforeSubscribe(request) {
request.query.equalTo('yolo', 'abc');
},
};
spyOn(hook, 'beforeSubscribe').and.callThrough();
Parse.Cloud.beforeSubscribe('TestObject', hook.beforeSubscribe);
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('update', () => {
fail('beforeSubscribe should restrict subscription');
});
subscription.on('enter', object => {
if (object.get('yolo') === 'abc') {
done();
} else {
fail('beforeSubscribe should restrict queries');
}
});
object.set({ yolo: 'bar' });
await object.save();
object.set({ yolo: 'abc' });
await object.save();
expect(hook.beforeSubscribe).toHaveBeenCalled();
});
it('can return a new beforeSubscribe query', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
Parse.Cloud.beforeSubscribe(TestObject, request => {
const query = new Parse.Query(TestObject);
query.equalTo('foo', 'yolo');
request.query = query;
});
const query = new Parse.Query(TestObject);
query.equalTo('foo', 'bar');
const subscription = await query.subscribe();
subscription.on('create', object => {
expect(object.get('foo')).toBe('yolo');
done();
});
const object = new TestObject();
object.set({ foo: 'yolo' });
await object.save();
});
it('can handle select beforeSubscribe query', async done => {
Parse.Cloud.beforeSubscribe(TestObject, request => {
const query = request.query;
query.select('yolo');
});
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('update', object => {
expect(object.get('foo')).toBeUndefined();
expect(object.get('yolo')).toBe('abc');
done();
});
object.set({ foo: 'bar', yolo: 'abc' });
await object.save();
});
it('LiveQuery with ACL', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['Chat'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const user = new Parse.User();
user.setUsername('username');
user.setPassword('password');
await user.signUp();
const calls = {
beforeConnect(req) {
expect(req.event).toBe('connect');
expect(req.clients).toBe(0);
expect(req.subscriptions).toBe(0);
expect(req.useMasterKey).toBe(false);
expect(req.installationId).toBeDefined();
expect(req.client).toBeDefined();
},
beforeSubscribe(req) {
expect(req.op).toBe('subscribe');
expect(req.requestId).toBe(1);
expect(req.query).toBeDefined();
expect(req.user).toBeDefined();
},
afterLiveQueryEvent(req) {
expect(req.user).toBeDefined();
expect(req.object.get('foo')).toBe('bar');
},
create(object) {
expect(object.get('foo')).toBe('bar');
},
delete(object) {
expect(object.get('foo')).toBe('bar');
},
};
for (const key in calls) {
spyOn(calls, key).and.callThrough();
}
Parse.Cloud.beforeConnect(calls.beforeConnect);
Parse.Cloud.beforeSubscribe('Chat', calls.beforeSubscribe);
Parse.Cloud.afterLiveQueryEvent('Chat', calls.afterLiveQueryEvent);
const chatQuery = new Parse.Query('Chat');
const subscription = await chatQuery.subscribe();
subscription.on('create', calls.create);
subscription.on('delete', calls.delete);
const object = new Parse.Object('Chat');
const acl = new Parse.ACL(user);
object.setACL(acl);
object.set({ foo: 'bar' });
await object.save();
await object.destroy();
await sleep(200);
for (const key in calls) {
expect(calls[key]).toHaveBeenCalled();
}
});
it('LiveQuery should work with changing role', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['Chat'],
},
startLiveQueryServer: true,
});
const user = new Parse.User();
user.setUsername('username');
user.setPassword('password');
await user.signUp();
const role = new Parse.Role('Test', new Parse.ACL(user));
await role.save();
const chatQuery = new Parse.Query('Chat');
const subscription = await chatQuery.subscribe();
subscription.on('create', () => {
fail('should not call create as user is not part of role.');
});
const object = new Parse.Object('Chat');
const acl = new Parse.ACL();
acl.setRoleReadAccess(role, true);
object.setACL(acl);
object.set({ foo: 'bar' });
await object.save(null, { useMasterKey: true });
role.getUsers().add(user);
await sleep(1000);
await role.save();
await sleep(1000);
object.set('foo', 'yolo');
await Promise.all([
new Promise(resolve => {
subscription.on('update', obj => {
expect(obj.get('foo')).toBe('yolo');
expect(obj.getACL().toJSON()).toEqual({ 'role:Test': { read: true } });
resolve();
});
}),
object.save(null, { useMasterKey: true }),
]);
});
it('liveQuery on Session class', async done => {
await reconfigureServer({
liveQuery: { classNames: [Parse.Session] },
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const user = new Parse.User();
user.setUsername('username');
user.setPassword('password');
await user.signUp();
const query = new Parse.Query(Parse.Session);
const subscription = await query.subscribe();
subscription.on('create', async obj => {
expect(obj.get('user').id).toBe(user.id);
expect(obj.get('createdWith')).toEqual({ action: 'login', authProvider: 'password' });
expect(obj.get('expiresAt')).toBeInstanceOf(Date);
expect(obj.get('installationId')).toBeDefined();
expect(obj.get('createdAt')).toBeInstanceOf(Date);
expect(obj.get('updatedAt')).toBeInstanceOf(Date);
done();
});
await Parse.User.logIn('username', 'password');
});
it('prevent liveQuery on Session class when not logged in', async () => {
await reconfigureServer({
liveQuery: {
classNames: [Parse.Session],
},
startLiveQueryServer: true,
});
const query = new Parse.Query(Parse.Session);
await expectAsync(query.subscribe()).toBeRejectedWith(new Error('Invalid session token'));
});
it('handle invalid websocket payload length', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
websocketTimeout: 100,
});
const object = new TestObject();
await object.save();
const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
// All control frames must have a payload length of 125 bytes or less.
// https://tools.ietf.org/html/rfc6455#section-5.5
//
// 0x89 = 10001001 = ping
// 0xfe = 11111110 = first bit is masking the remaining 7 are 1111110 or 126 the payload length
// https://tools.ietf.org/html/rfc6455#section-5.2
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
client.socket._socket.write(Buffer.from([0x89, 0xfe]));
subscription.on('update', async object => {
expect(object.get('foo')).toBe('bar');
done();
});
// Wait for Websocket timeout to reconnect
setTimeout(async () => {
object.set({ foo: 'bar' });
await object.save();
}, 1000);
});
it('should execute live query update on email validation', async done => {
const emailAdapter = {
sendVerificationEmail: () => {},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
await reconfigureServer({
maintenanceKey: 'test2',
liveQuery: {
classNames: [Parse.User],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
websocketTimeout: 100,
appName: 'liveQueryEmailValidation',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 20, // 0.5 second
publicServerURL: 'http://localhost:8378/1',
}).then(() => {
const user = new Parse.User();
user.set('password', 'asdf');
user.set('email', 'asdf@example.com');
user.set('username', 'zxcv');
user
.signUp()
.then(() => {
const config = Config.get('test');
return config.database.find(
'_User',
{
username: 'zxcv',
},
{},
Auth.maintenance(config)
);
})
.then(async results => {
const foundUser = results[0];
const query = new Parse.Query('_User');
query.equalTo('objectId', foundUser.objectId);
const subscription = await query.subscribe();
subscription.on('update', async object => {
expect(object).toBeDefined();
expect(object.get('emailVerified')).toBe(true);
done();
});
const userController = new UserController(emailAdapter, 'test', {
verifyUserEmails: true,
});
userController.verifyEmail(foundUser.username, foundUser._email_verify_token);
});
});
});
it('should not broadcast event to client with invalid session token - avisory GHSA-2xm2-xj2q-qgpj', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
liveQueryServerOptions: {
cacheTimeout: 100,
},
startLiveQueryServer: true,
verbose: false,
silent: true,
cacheTTL: 100,
});
const user = new Parse.User();
user.setUsername('username');
user.setPassword('password');
await user.signUp();
const obj1 = new Parse.Object('TestObject');
const obj1ACL = new Parse.ACL();
obj1ACL.setPublicReadAccess(false);
obj1ACL.setReadAccess(user, true);
obj1.setACL(obj1ACL);
const obj2 = new Parse.Object('TestObject');
const obj2ACL = new Parse.ACL();
obj2ACL.setPublicReadAccess(false);
obj2ACL.setReadAccess(user, true);
obj2.setACL(obj2ACL);
const query = new Parse.Query('TestObject');
const subscription = await query.subscribe();
subscription.on('create', obj => {
if (obj.id !== obj1.id) {
done.fail('should not fire');
}
});
await obj1.save();
await Parse.User.logOut();
await new Promise(resolve => setTimeout(resolve, 200));
await obj2.save();
await new Promise(resolve => setTimeout(resolve, 200));
done();
});
it('should strip out session token in LiveQuery', async () => {
await reconfigureServer({
liveQuery: { classNames: ['_User'] },
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const user = new Parse.User();
user.setUsername('username');
user.setPassword('password');
user.set('foo', 'bar');
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
user.setACL(acl);
const query = new Parse.Query(Parse.User);
query.equalTo('foo', 'bar');
const subscription = await query.subscribe();
const events = ['create', 'update', 'enter', 'leave', 'delete'];
const response = (obj, prev) => {
expect(obj.get('sessionToken')).toBeUndefined();
expect(obj.sessionToken).toBeUndefined();
expect(prev && prev.sessionToken).toBeUndefined();
if (prev && prev.get) {
expect(prev.get('sessionToken')).toBeUndefined();
}
};
const calls = {};
for (const key of events) {
calls[key] = response;
spyOn(calls, key).and.callThrough();
subscription.on(key, calls[key]);
}
await user.signUp();
user.unset('foo');
await user.save();
user.set('foo', 'bar');
await user.save();
user.set('yolo', 'bar');
await user.save();
await user.destroy();
await new Promise(resolve => setTimeout(resolve, 10));
for (const key of events) {
expect(calls[key]).toHaveBeenCalled();
}
});
it('should strip out protected fields', async () => {
await reconfigureServer({
liveQuery: { classNames: ['Test'] },
startLiveQueryServer: true,
});
const obj1 = new Parse.Object('Test');
obj1.set('foo', 'foo');
obj1.set('bar', 'bar');
obj1.set('qux', 'qux');
await obj1.save();
const config = Config.get(Parse.applicationId);
const schemaController = await config.database.loadSchema();
await schemaController.updateClass(
'Test',
{},
{
get: { '*': true },
find: { '*': true },
update: { '*': true },
protectedFields: {
'*': ['foo'],
},
}
);
const object = await obj1.fetch();
expect(object.get('foo')).toBe(undefined);
expect(object.get('bar')).toBeDefined();
expect(object.get('qux')).toBeDefined();
const subscription = await new Parse.Query('Test').subscribe();
await Promise.all([
new Promise(resolve => {
subscription.on('update', (obj, original) => {
expect(obj.get('foo')).toBe(undefined);
expect(obj.get('bar')).toBeDefined();
expect(obj.get('qux')).toBeDefined();
expect(original.get('foo')).toBe(undefined);
expect(original.get('bar')).toBeDefined();
expect(original.get('qux')).toBeDefined();
resolve();
});
}),
obj1.save({ foo: 'abc' }),
]);
});
it('can subscribe to query and return object with withinKilometers with last parameter on update', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const object = new TestObject();
const firstPoint = new Parse.GeoPoint({ latitude: 40.0, longitude: -30.0 });
object.set({ location: firstPoint });
await object.save();
// unsorted will use $centerSphere operator
const sorted = false;
const query = new Parse.Query(TestObject);
query.withinKilometers(
'location',
new Parse.GeoPoint({ latitude: 40.0, longitude: -30.0 }),
2,
sorted
);
const subscription = await query.subscribe();
subscription.on('update', obj => {
expect(obj.id).toBe(object.id);
done();
});
const secondPoint = new Parse.GeoPoint({ latitude: 40.0, longitude: -30.0 });
object.set({ location: secondPoint });
await object.save();
});
it('does shutdown liveQuery server', async () => {
await reconfigureServer({ appId: 'test_app_id' });
const config = {
appId: 'hello_test',
masterKey: 'world',
port: 1345,
mountPath: '/1',
serverURL: 'http://localhost:1345/1',
liveQuery: {
classNames: ['Yolo'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
};
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
config.databaseAdapter = new databaseAdapter.constructor({
uri: databaseURI,
collectionPrefix: 'test_',
});
config.filesAdapter = defaultConfiguration.filesAdapter;
}
const server = await ParseServer.startApp(config);
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
client.serverURL = 'ws://localhost:1345/1';
const query = await new Parse.Query('Yolo').subscribe();
await Promise.all([
server.handleShutdown(),
new Promise(resolve => query.on('close', resolve)),
]);
await new Promise(resolve => setTimeout(resolve, 100));
expect(server.liveQueryServer.server.address()).toBeNull();
expect(server.liveQueryServer.subscriber.isOpen).toBeFalse();
await new Promise(resolve => server.server.close(resolve));
});
it('prevent afterSave trigger if not exists', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
spyOn(triggers, 'maybeRunTrigger').and.callThrough();
const object1 = new TestObject();
const object2 = new TestObject();
const object3 = new TestObject();
await Parse.Object.saveAll([object1, object2, object3]);
expect(triggers.maybeRunTrigger).toHaveBeenCalledTimes(0);
expect(object1.id).toBeDefined();
expect(object2.id).toBeDefined();
expect(object3.id).toBeDefined();
});
it('triggers query event with constraint not equal to null', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const spy = {
create(obj) {
expect(obj.attributes.foo).toEqual('bar');
},
};
const createSpy = spyOn(spy, 'create');
const query = new Parse.Query(TestObject);
query.notEqualTo('foo', null);
const subscription = await query.subscribe();
subscription.on('create', spy.create);
const object1 = new TestObject();
object1.set('foo', 'bar');
await object1.save();
await new Promise(resolve => setTimeout(resolve, 100));
expect(createSpy).toHaveBeenCalledTimes(1);
});
});