Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
"version": "9.6.0-alpha.55",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"exports": {
".": "./lib/index.js",
"./cloud": {
"types": "./types/cloud.d.ts",
"default": "./lib/cloud.js"
}
},
"repository": {
"type": "git",
"url": "https://github.com/parse-community/parse-server"
Expand Down
159 changes: 159 additions & 0 deletions spec/TriggerStore.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
'use strict';

const Parse = require('parse/node');
const request = require('../lib/request');

describe('TriggerStore', () => {
describe('validator cleanup', () => {
it('should remove stale validator when re-registering function without one', async () => {
Parse.Cloud.define(
'validatedFunc',
() => 'ok',
{ requireUser: true }
);
await expectAsync(
Parse.Cloud.run('validatedFunc')
).toBeRejectedWith(
new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Validation failed. Please login to continue.')
);
Parse.Cloud.define('validatedFunc', () => 'ok');
const result = await Parse.Cloud.run('validatedFunc');
expect(result).toBe('ok');
});

it('should remove stale validator when re-registering trigger without one', async () => {
Parse.Cloud.beforeSave('StaleValidatorTest', () => {}, { requireMaster: true });
await expectAsync(
new Parse.Object('StaleValidatorTest').save()
).toBeRejectedWith(
new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Validation failed. Master key is required to complete this request.')
);
Parse.Cloud.beforeSave('StaleValidatorTest', () => {});
const obj = new Parse.Object('StaleValidatorTest');
await obj.save();
expect(obj.id).toBeDefined();
});
});

describe('removal cleanup', () => {
it('should remove validators when removing hooks via _removeAllHooks', async () => {
Parse.Cloud.define(
'hookCleanupFunc',
() => 'ok',
{ requireUser: true }
);
await expectAsync(
Parse.Cloud.run('hookCleanupFunc')
).toBeRejectedWith(
new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Validation failed. Please login to continue.')
);
Parse.Cloud._removeAllHooks();
Parse.Cloud.define('hookCleanupFunc', () => 'ok');
const result = await Parse.Cloud.run('hookCleanupFunc');
expect(result).toBe('ok');
});
});

describe('invalid names', () => {
it('should silently reject function names with quotes', async () => {
Parse.Cloud.define("test'injection", () => 'bad');
await expectAsync(
Parse.Cloud.run("test'injection")
).toBeRejectedWith(
new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid function: "test\'injection"')
);
});

it('should silently reject function names with backticks', async () => {
Parse.Cloud.define('test`injection', () => 'bad');
await expectAsync(
Parse.Cloud.run('test`injection')
).toBeRejectedWith(
new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid function: "test`injection"')
);
});
});

describe('_PushStatus validation', () => {
it('should reject beforeSave on _PushStatus', () => {
expect(() => {
Parse.Cloud.beforeSave('_PushStatus', () => {});
}).toThrow('Only afterSave is allowed on _PushStatus');
});

it('should reject beforeDelete on _PushStatus', () => {
expect(() => {
Parse.Cloud.beforeDelete('_PushStatus', () => {});
}).toThrow('Only afterSave is allowed on _PushStatus');
});

it('should reject beforeFind on _PushStatus', () => {
expect(() => {
Parse.Cloud.beforeFind('_PushStatus', () => {});
}).toThrow('Only afterSave is allowed on _PushStatus');
});

it('should reject afterDelete on _PushStatus', () => {
expect(() => {
Parse.Cloud.afterDelete('_PushStatus', () => {});
}).toThrow('Only afterSave is allowed on _PushStatus');
});

it('should allow afterSave on _PushStatus', () => {
expect(() => {
Parse.Cloud.afterSave('_PushStatus', () => {});
}).not.toThrow();
});
});

describe('Parse.Server setter', () => {
it('should merge properties without losing existing config', () => {
const originalKeys = Object.keys(Parse.Server);
Parse.Server = { customProp: true };
const config = Parse.Server;
expect(config.customProp).toBe(true);
for (const key of ['appId', 'masterKey', 'serverURL']) {
expect(config[key]).toBeDefined();
}
expect(Object.keys(config).length).toBeGreaterThanOrEqual(originalKeys.length);
});
});

describe('live query event handlers', () => {
it('should forward events to cloud code handler', async () => {
let receivedData;
Parse.Cloud.onLiveQueryEvent(data => {
receivedData = data;
});
const triggers = require('../lib/triggers');
triggers.runLiveQueryEventHandlers({ event: 'test' });
expect(receivedData).toEqual({ event: 'test' });
});
});

describe('beforeLogin validator', () => {
it('should enforce validator on beforeLogin', async () => {
Parse.Cloud.beforeLogin(() => {}, { requireMaster: true });
const user = new Parse.User();
user.setUsername('loginval_user');
user.setPassword('password');
await user.signUp();
await Parse.User.logOut();
await expectAsync(
Parse.User.logIn('loginval_user', 'password')
).toBeRejectedWith(
new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Validation failed. Master key is required to complete this request.')
);
});
});

describe('useMasterKey deprecation', () => {
it('should warn on useMasterKey call', () => {
const spy = spyOn(console, 'warn');
Parse.Cloud.useMasterKey();
expect(spy).toHaveBeenCalledWith(
jasmine.stringContaining('useMasterKey is deprecated')
);
});
});
});
35 changes: 9 additions & 26 deletions src/ParseServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
var batch = require('./batch'),
express = require('express'),
middlewares = require('./middlewares'),
Parse = require('parse/node').Parse,
{ parse } = require('graphql'),
path = require('path'),
fs = require('fs');
Expand Down Expand Up @@ -46,9 +45,7 @@ import Deprecator from './Deprecator/Deprecator';
import { DefinedSchemas } from './SchemaMigrations/DefinedSchemas';
import OptionsDefinitions from './Options/Definitions';
import { resolvingPromise, Connections } from './TestUtils';

// Mutate the Parse object to add the Cloud Code handlers
addParseCloud();
import { LegacyCloud } from './cloud-code/LegacyCloud';

// Track connections to destroy them on shutdown
const connections = new Connections();
Expand All @@ -61,6 +58,7 @@ class ParseServer {
server: any;
expressApp: any;
liveQueryServer: any;
private legacyCloud: LegacyCloud;
/**
* @constructor
* @param {ParseServerOptions} options the parse server initialization options
Expand Down Expand Up @@ -128,9 +126,9 @@ class ParseServer {
javascriptKey,
serverURL = requiredParameter('You must provide a serverURL!'),
} = options;
// Initialize the node client SDK automatically
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
Parse.serverURL = serverURL;
// Initialize the registrar and legacy cloud SDK
this.legacyCloud = new LegacyCloud();
this.legacyCloud.initialize({ appId, masterKey, javascriptKey, serverURL });
Config.validateOptions(options);
const allControllers = controllers.getControllers(options);

Expand Down Expand Up @@ -163,6 +161,7 @@ class ParseServer {
schema,
liveQueryController,
} = this.config;
const Parse = this.legacyCloud.Parse;
try {
await databaseController.performInitialization();
} catch (e) {
Expand All @@ -184,8 +183,8 @@ class ParseServer {
}
startupPromises.push(liveQueryController.connect());
await Promise.all(startupPromises);
this.legacyCloud.bindToParseCloud();
if (cloud) {
addParseCloud();
if (typeof cloud === 'function') {
await Promise.resolve(cloud(Parse));
} else if (typeof cloud === 'string') {
Expand Down Expand Up @@ -373,6 +372,7 @@ class ParseServer {
});
}
if (process.env.PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS === '1' || directAccess) {
const Parse = require('parse/node').Parse;
Parse.CoreManager.setRESTController(ParseServerRESTController(appId, appRouter));
}
return api;
Expand Down Expand Up @@ -535,6 +535,7 @@ class ParseServer {
}

static async verifyServerUrl() {
const Parse = require('parse/node').Parse;
// perform a health check on the serverURL value
if (Parse.serverURL) {
const isValidHttpUrl = string => {
Expand Down Expand Up @@ -577,24 +578,6 @@ class ParseServer {
}
}

function addParseCloud() {
const ParseCloud = require('./cloud-code/Parse.Cloud');
const ParseServer = require('./cloud-code/Parse.Server');
Object.defineProperty(Parse, 'Server', {
get() {
const conf = Config.get(Parse.applicationId);
return { ...conf, ...ParseServer };
},
set(newVal) {
newVal.appId = Parse.applicationId;
Config.put(newVal);
},
configurable: true,
});
Object.assign(Parse.Cloud, ParseCloud);
global.Parse = Parse;
}

function injectDefaults(options: ParseServerOptions) {
Object.keys(defaults).forEach(key => {
if (!Object.prototype.hasOwnProperty.call(options, key)) {
Expand Down
Loading
Loading