Unique indexes (#1971)
* Add unique indexing * Add unique indexing for username/email * WIP * Finish unique indexes * Notes on how to upgrade to 2.3.0 safely * index on unique-indexes: c454180 Revert "Log objects rather than JSON stringified objects (#1922)" * reconfigure username/email tests * Start dealing with test shittyness * Remove tests for files that we are removing * most tests passing * fix failing test * Make specific server config for tests async * Fix more tests * fix more tests * Fix another test * fix more tests * Fix email validation * move some stuff around * Destroy server to ensure all connections are gone * Fix broken cloud code * Save callback to variable * no need to delete non existant cloud * undo * Fix all tests where connections are left open after server closes. * Fix issues caused by missing gridstore adapter * Update guide for 2.3.0 and fix final tests * use strict * don't use features that won't work in node 4 * Fix syntax error * Fix typos * Add duplicate finding command * Update 2.3.0.md
This commit is contained in:
82
2.3.0.md
Normal file
82
2.3.0.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Upgrading Parse Server to version 2.3.0
|
||||||
|
|
||||||
|
Parse Server version 2.3.0 begins using unique indexes to ensure User's username and email are unique. This is not a backwards incompatable change, but it may in some cases cause a significant performance regression until the index finishes building. Building the unique index before upgrading your Parse Server version will eliminate the performance impact, and is a recommended step before upgrading any app to Parse Server 2.3.0. New apps starting with version 2.3.0 do not need to take any steps before beginning their project.
|
||||||
|
|
||||||
|
If you are using MongoDB in Cluster or Replica Set mode, we recommend reading Mongo's [documentation on index building](https://docs.mongodb.com/v3.0/tutorial/build-indexes-on-replica-sets/) first. If you are not using these features, you can execute the following commands from the Mongo shell to build the unique index. You may also want to create a backup first.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Select the database that your Parse App uses
|
||||||
|
use parse;
|
||||||
|
|
||||||
|
// Select the collection your Parse App uses for users. For migrated apps, this probably includes a collectionPrefix.
|
||||||
|
var coll = db['your_prefix:_User'];
|
||||||
|
|
||||||
|
// You can check if the indexes already exists by running coll.getIndexes()
|
||||||
|
coll.getIndexes();
|
||||||
|
|
||||||
|
// The indexes you want should look like this. If they already exist, you can skip creating them.
|
||||||
|
{
|
||||||
|
"v" : 1,
|
||||||
|
"unique" : true,
|
||||||
|
"key" : {
|
||||||
|
"username" : 1
|
||||||
|
},
|
||||||
|
"name" : "username_1",
|
||||||
|
"ns" : "parse.your_prefix:_User",
|
||||||
|
"background" : true,
|
||||||
|
"sparse" : true
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"v" : 1,
|
||||||
|
"unique" : true,
|
||||||
|
"key" : {
|
||||||
|
"email" : 1
|
||||||
|
},
|
||||||
|
"name" : "email_1",
|
||||||
|
"ns" : "parse.your_prefix:_User",
|
||||||
|
"background" : true,
|
||||||
|
"sparse" : true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the username index.
|
||||||
|
// "background: true" is mandatory and avoids downtime while the index builds.
|
||||||
|
// "sparse: true" is also mandatory because Parse Server uses sparse indexes.
|
||||||
|
coll.ensureIndex({ username: 1 }, { background: true, unique: true, sparse: true });
|
||||||
|
|
||||||
|
// Create the email index.
|
||||||
|
// "background: true" is still mandatory.
|
||||||
|
// "sparse: true" is also mandatory both because Parse Server uses sparse indexes, and because email addresses are not required by the Parse API.
|
||||||
|
coll.ensureIndex({ email: 1 }, { background: true, unique: true, sparse: true });
|
||||||
|
```
|
||||||
|
|
||||||
|
There are some issues you may run into during this process:
|
||||||
|
|
||||||
|
## Mongo complains that the index already exists, but with different options
|
||||||
|
|
||||||
|
In this case, you will need to remove the incorrect index. If your app relies on the existence of the index in order to be performant, you can create a new index, with "-1" for the direction of the field, so that it counts as different options. Then, drop the conflicting index, and create the unique index.
|
||||||
|
|
||||||
|
## There is already non-unique data in the username or email field
|
||||||
|
|
||||||
|
This is possible if you have explicitly set some user's emails to null. If this is bogus data, and those null fields shoud be unset, you can unset the null emails with this command. If your app relies on the difference between null and unset emails, you will need to upgrade your app to treat null and unset emails the same before building the index and upgrading to Parse Server 2.3.0.
|
||||||
|
|
||||||
|
```js
|
||||||
|
coll.update({ email: { $exists: true, $eq: null } }, { $unset: { email: '' } }, { multi: true })
|
||||||
|
```
|
||||||
|
|
||||||
|
## There is already non-unique data in the username or email field, and it's not nulls
|
||||||
|
|
||||||
|
This is possible due to a race condition in previous versions of Parse Server. If you have this problem, it is unlikely that you have a lot of rows with duplicate data. We recommend you clean up the data manually, by removing or modifying the offending rows.
|
||||||
|
|
||||||
|
This command, can be used to find the duplicate data:
|
||||||
|
|
||||||
|
```js
|
||||||
|
coll.aggregate([
|
||||||
|
{$match: {"username": {"$ne": null}}},
|
||||||
|
{$group: {_id: "$username", uniqueIds: {$addToSet: "$_id"}, count: {$sum: 1}}},
|
||||||
|
{$match: {count: {"$gt": 1}}},
|
||||||
|
{$project: {id: "$uniqueIds", username: "$_id", _id : 0} },
|
||||||
|
{$unwind: "$id" },
|
||||||
|
{$out: '_duplicates'} // Save the list of duplicates to a new, "_duplicates collection. Remove this line to just output the list.
|
||||||
|
], {allowDiskUse:true})
|
||||||
|
```
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
"parse-server-simple-mailgun-adapter": "^1.0.0",
|
"parse-server-simple-mailgun-adapter": "^1.0.0",
|
||||||
"redis": "^2.5.0-1",
|
"redis": "^2.5.0-1",
|
||||||
"request": "^2.65.0",
|
"request": "^2.65.0",
|
||||||
|
"request-promise": "^3.0.0",
|
||||||
"tv4": "^1.2.7",
|
"tv4": "^1.2.7",
|
||||||
"winston": "^2.1.1",
|
"winston": "^2.1.1",
|
||||||
"winston-daily-rotate-file": "^1.0.1",
|
"winston-daily-rotate-file": "^1.0.1",
|
||||||
|
|||||||
@@ -1,33 +1,28 @@
|
|||||||
"use strict"
|
"use strict"
|
||||||
const Parse = require("parse/node");
|
const Parse = require("parse/node");
|
||||||
const request = require('request');
|
const request = require('request');
|
||||||
|
const rp = require('request-promise');
|
||||||
const InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').InMemoryCacheAdapter;
|
const InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').InMemoryCacheAdapter;
|
||||||
|
|
||||||
describe('Cloud Code', () => {
|
describe('Cloud Code', () => {
|
||||||
it('can load absolute cloud code file', done => {
|
it('can load absolute cloud code file', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({ cloud: __dirname + '/cloud/cloudCodeRelativeFile.js' })
|
||||||
serverURL: 'http://localhost:8378/1',
|
.then(() => {
|
||||||
appId: 'test',
|
Parse.Cloud.run('cloudCodeInFile', {}, result => {
|
||||||
masterKey: 'test',
|
expect(result).toEqual('It is possible to define cloud code in a file.');
|
||||||
cloud: __dirname + '/cloud/cloudCodeRelativeFile.js'
|
done();
|
||||||
});
|
});
|
||||||
Parse.Cloud.run('cloudCodeInFile', {}, result => {
|
})
|
||||||
expect(result).toEqual('It is possible to define cloud code in a file.');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can load relative cloud code file', done => {
|
it('can load relative cloud code file', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({ cloud: './spec/cloud/cloudCodeAbsoluteFile.js' })
|
||||||
serverURL: 'http://localhost:8378/1',
|
.then(() => {
|
||||||
appId: 'test',
|
Parse.Cloud.run('cloudCodeInFile', {}, result => {
|
||||||
masterKey: 'test',
|
expect(result).toEqual('It is possible to define cloud code in a file.');
|
||||||
cloud: './spec/cloud/cloudCodeAbsoluteFile.js'
|
done();
|
||||||
});
|
});
|
||||||
Parse.Cloud.run('cloudCodeInFile', {}, result => {
|
})
|
||||||
expect(result).toEqual('It is possible to define cloud code in a file.');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can create functions', done => {
|
it('can create functions', done => {
|
||||||
@@ -568,67 +563,75 @@ describe('Cloud Code', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('clears out the user cache for all sessions when the user is changed', done => {
|
it('clears out the user cache for all sessions when the user is changed', done => {
|
||||||
|
let session1;
|
||||||
|
let session2;
|
||||||
|
let user;
|
||||||
const cacheAdapter = new InMemoryCacheAdapter({ ttl: 100000000 });
|
const cacheAdapter = new InMemoryCacheAdapter({ ttl: 100000000 });
|
||||||
setServerConfiguration(Object.assign({}, defaultConfiguration, { cacheAdapter: cacheAdapter }));
|
reconfigureServer({ cacheAdapter })
|
||||||
Parse.Cloud.define('checkStaleUser', (request, response) => {
|
.then(() => {
|
||||||
response.success(request.user.get('data'));
|
Parse.Cloud.define('checkStaleUser', (request, response) => {
|
||||||
});
|
response.success(request.user.get('data'));
|
||||||
|
});
|
||||||
|
|
||||||
let user = new Parse.User();
|
user = new Parse.User();
|
||||||
user.set('username', 'test');
|
user.set('username', 'test');
|
||||||
user.set('password', 'moon-y');
|
user.set('password', 'moon-y');
|
||||||
user.set('data', 'first data');
|
user.set('data', 'first data');
|
||||||
user.signUp()
|
return user.signUp();
|
||||||
|
})
|
||||||
.then(user => {
|
.then(user => {
|
||||||
let session1 = user.getSessionToken();
|
session1 = user.getSessionToken();
|
||||||
request.get({
|
return rp({
|
||||||
url: 'http://localhost:8378/1/login?username=test&password=moon-y',
|
uri: 'http://localhost:8378/1/login?username=test&password=moon-y',
|
||||||
json: true,
|
json: true,
|
||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': 'test',
|
'X-Parse-Application-Id': 'test',
|
||||||
'X-Parse-REST-API-Key': 'rest',
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
},
|
},
|
||||||
}, (error, response, body) => {
|
})
|
||||||
let session2 = body.sessionToken;
|
})
|
||||||
|
.then(body => {
|
||||||
|
session2 = body.sessionToken;
|
||||||
|
|
||||||
//Ensure both session tokens are in the cache
|
//Ensure both session tokens are in the cache
|
||||||
Parse.Cloud.run('checkStaleUser')
|
return Parse.Cloud.run('checkStaleUser')
|
||||||
.then(() => {
|
})
|
||||||
request.post({
|
.then(() => rp({
|
||||||
url: 'http://localhost:8378/1/functions/checkStaleUser',
|
method: 'POST',
|
||||||
json: true,
|
uri: 'http://localhost:8378/1/functions/checkStaleUser',
|
||||||
headers: {
|
json: true,
|
||||||
'X-Parse-Application-Id': 'test',
|
headers: {
|
||||||
'X-Parse-REST-API-Key': 'rest',
|
'X-Parse-Application-Id': 'test',
|
||||||
'X-Parse-Session-Token': session2,
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
}
|
'X-Parse-Session-Token': session2,
|
||||||
}, (error, response, body) => {
|
}
|
||||||
Parse.Promise.all([cacheAdapter.get('test:user:' + session1), cacheAdapter.get('test:user:' + session2)])
|
}))
|
||||||
.then(cachedVals => {
|
.then(() => Parse.Promise.all([cacheAdapter.get('test:user:' + session1), cacheAdapter.get('test:user:' + session2)]))
|
||||||
expect(cachedVals[0].objectId).toEqual(user.id);
|
.then(cachedVals => {
|
||||||
expect(cachedVals[1].objectId).toEqual(user.id);
|
expect(cachedVals[0].objectId).toEqual(user.id);
|
||||||
|
expect(cachedVals[1].objectId).toEqual(user.id);
|
||||||
|
|
||||||
//Change with session 1 and then read with session 2.
|
//Change with session 1 and then read with session 2.
|
||||||
user.set('data', 'second data');
|
user.set('data', 'second data');
|
||||||
user.save()
|
return user.save()
|
||||||
.then(() => {
|
})
|
||||||
request.post({
|
.then(() => rp({
|
||||||
url: 'http://localhost:8378/1/functions/checkStaleUser',
|
method: 'POST',
|
||||||
json: true,
|
uri: 'http://localhost:8378/1/functions/checkStaleUser',
|
||||||
headers: {
|
json: true,
|
||||||
'X-Parse-Application-Id': 'test',
|
headers: {
|
||||||
'X-Parse-REST-API-Key': 'rest',
|
'X-Parse-Application-Id': 'test',
|
||||||
'X-Parse-Session-Token': session2,
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
}
|
'X-Parse-Session-Token': session2,
|
||||||
}, (error, response, body) => {
|
}
|
||||||
expect(body.result).toEqual('second data');
|
}))
|
||||||
done();
|
.then(body => {
|
||||||
})
|
expect(body.result).toEqual('second data');
|
||||||
});
|
done();
|
||||||
});
|
})
|
||||||
});
|
.catch(error => {
|
||||||
});
|
fail(JSON.stringify(error));
|
||||||
});
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
let DatabaseAdapter = require('../src/DatabaseAdapter');
|
|
||||||
|
|
||||||
describe('DatabaseAdapter', () => {
|
|
||||||
it('options and URI are available to adapter', done => {
|
|
||||||
DatabaseAdapter.setAppDatabaseURI('optionsTest', 'mongodb://localhost:27017/optionsTest');
|
|
||||||
DatabaseAdapter.setAppDatabaseOptions('optionsTest', {foo: "bar"});
|
|
||||||
let optionsTestDatabaseConnection = DatabaseAdapter.getDatabaseConnection('optionsTest');
|
|
||||||
|
|
||||||
expect(optionsTestDatabaseConnection).toEqual(jasmine.any(Object));
|
|
||||||
expect(optionsTestDatabaseConnection.adapter._mongoOptions).toEqual(jasmine.any(Object));
|
|
||||||
expect(optionsTestDatabaseConnection.adapter._mongoOptions.foo).toBe("bar");
|
|
||||||
|
|
||||||
DatabaseAdapter.setAppDatabaseURI('noOptionsTest', 'mongodb://localhost:27017/noOptionsTest');
|
|
||||||
let noOptionsTestDatabaseConnection = DatabaseAdapter.getDatabaseConnection('noOptionsTest');
|
|
||||||
|
|
||||||
expect(noOptionsTestDatabaseConnection).toEqual(jasmine.any(Object));
|
|
||||||
expect(noOptionsTestDatabaseConnection.adapter._mongoOptions).toEqual(jasmine.any(Object));
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
let DatabaseController = require('../src/Controllers/DatabaseController');
|
|
||||||
let MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
|
||||||
|
|
||||||
describe('DatabaseController', () => {
|
|
||||||
it('can be constructed', done => {
|
|
||||||
let adapter = new MongoStorageAdapter({
|
|
||||||
uri: 'mongodb://localhost:27017/test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
});
|
|
||||||
let databaseController = new DatabaseController(adapter);
|
|
||||||
databaseController.connect().then(done, error => {
|
|
||||||
console.log('error', error.stack);
|
|
||||||
fail();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -52,9 +52,9 @@ describe('error logs', () => {
|
|||||||
describe('verbose logs', () => {
|
describe('verbose logs', () => {
|
||||||
|
|
||||||
it("mask sensitive information in _User class", (done) => {
|
it("mask sensitive information in _User class", (done) => {
|
||||||
let customConfig = Object.assign({}, defaultConfiguration, {verbose: true});
|
reconfigureServer({ verbose: true })
|
||||||
setServerConfiguration(customConfig);
|
.then(() => createTestUser())
|
||||||
createTestUser().then(() => {
|
.then(() => {
|
||||||
let fileLoggerAdapter = new FileLoggerAdapter();
|
let fileLoggerAdapter = new FileLoggerAdapter();
|
||||||
return fileLoggerAdapter.query({
|
return fileLoggerAdapter.query({
|
||||||
from: new Date(Date.now() - 500),
|
from: new Date(Date.now() - 500),
|
||||||
|
|||||||
@@ -28,26 +28,27 @@ describe('Parse.Push', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setServerConfiguration({
|
return reconfigureServer({
|
||||||
appId: Parse.applicationId,
|
appId: Parse.applicationId,
|
||||||
masterKey: Parse.masterKey,
|
masterKey: Parse.masterKey,
|
||||||
serverURL: Parse.serverURL,
|
serverURL: Parse.serverURL,
|
||||||
push: {
|
push: {
|
||||||
adapter: pushAdapter
|
adapter: pushAdapter
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
var installations = [];
|
||||||
|
while(installations.length != 10) {
|
||||||
|
var installation = new Parse.Object("_Installation");
|
||||||
|
installation.set("installationId", "installation_"+installations.length);
|
||||||
|
installation.set("deviceToken","device_token_"+installations.length)
|
||||||
|
installation.set("badge", installations.length);
|
||||||
|
installation.set("originalBadge", installations.length);
|
||||||
|
installation.set("deviceType", "ios");
|
||||||
|
installations.push(installation);
|
||||||
|
}
|
||||||
|
return Parse.Object.saveAll(installations);
|
||||||
});
|
});
|
||||||
|
|
||||||
var installations = [];
|
|
||||||
while(installations.length != 10) {
|
|
||||||
var installation = new Parse.Object("_Installation");
|
|
||||||
installation.set("installationId", "installation_"+installations.length);
|
|
||||||
installation.set("deviceToken","device_token_"+installations.length)
|
|
||||||
installation.set("badge", installations.length);
|
|
||||||
installation.set("originalBadge", installations.length);
|
|
||||||
installation.set("deviceType", "ios");
|
|
||||||
installations.push(installation);
|
|
||||||
}
|
|
||||||
return Parse.Object.saveAll(installations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should properly send push', (done) => {
|
it('should properly send push', (done) => {
|
||||||
@@ -110,7 +111,7 @@ describe('Parse.Push', () => {
|
|||||||
'X-Parse-Application-Id': 'test',
|
'X-Parse-Application-Id': 'test',
|
||||||
},
|
},
|
||||||
}, (error, response, body) => {
|
}, (error, response, body) => {
|
||||||
expect(body.results.length).toEqual(0);
|
expect(body.error).toEqual('unauthorized');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||||
|
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
const Parse = require("parse/node");
|
const Parse = require("parse/node");
|
||||||
let Config = require('../src/Config');
|
let Config = require('../src/Config');
|
||||||
|
let defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
|
||||||
|
var TestUtils = require('../src/index').TestUtils;
|
||||||
|
|
||||||
|
const requiredUserFields = { fields: Object.assign({}, defaultColumns._Default, defaultColumns._User) };
|
||||||
|
|
||||||
describe('miscellaneous', function() {
|
describe('miscellaneous', function() {
|
||||||
it('create a GameScore object', function(done) {
|
it('create a GameScore object', function(done) {
|
||||||
@@ -45,14 +50,172 @@ describe('miscellaneous', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fail to create a duplicate username', function(done) {
|
it('fail to create a duplicate username', done => {
|
||||||
createTestUser(function(data) {
|
let numCreated = 0;
|
||||||
createTestUser(function(data) {
|
let numFailed = 0;
|
||||||
fail('Should not have been able to save duplicate username.');
|
let p1 = createTestUser();
|
||||||
}, function(error) {
|
p1.then(user => {
|
||||||
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
|
numCreated++;
|
||||||
|
expect(numCreated).toEqual(1);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
numFailed++;
|
||||||
|
expect(numFailed).toEqual(1);
|
||||||
|
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
|
||||||
|
});
|
||||||
|
let p2 = createTestUser();
|
||||||
|
p2.then(user => {
|
||||||
|
numCreated++;
|
||||||
|
expect(numCreated).toEqual(1);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
numFailed++;
|
||||||
|
expect(numFailed).toEqual(1);
|
||||||
|
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
|
||||||
|
});
|
||||||
|
Parse.Promise.when([p1, p2])
|
||||||
|
.then(() => {
|
||||||
|
fail('one of the users should not have been created');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ensure that email is uniquely indexed', done => {
|
||||||
|
let numCreated = 0;
|
||||||
|
let numFailed = 0;
|
||||||
|
|
||||||
|
let user1 = new Parse.User();
|
||||||
|
user1.setPassword('asdf');
|
||||||
|
user1.setUsername('u1');
|
||||||
|
user1.setEmail('dupe@dupe.dupe');
|
||||||
|
let p1 = user1.signUp();
|
||||||
|
p1.then(user => {
|
||||||
|
numCreated++;
|
||||||
|
expect(numCreated).toEqual(1);
|
||||||
|
}, error => {
|
||||||
|
numFailed++;
|
||||||
|
expect(numFailed).toEqual(1);
|
||||||
|
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
|
||||||
|
});
|
||||||
|
|
||||||
|
let user2 = new Parse.User();
|
||||||
|
user2.setPassword('asdf');
|
||||||
|
user2.setUsername('u2');
|
||||||
|
user2.setEmail('dupe@dupe.dupe');
|
||||||
|
let p2 = user2.signUp();
|
||||||
|
p2.then(user => {
|
||||||
|
numCreated++;
|
||||||
|
expect(numCreated).toEqual(1);
|
||||||
|
}, error => {
|
||||||
|
numFailed++;
|
||||||
|
expect(numFailed).toEqual(1);
|
||||||
|
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
|
||||||
|
});
|
||||||
|
|
||||||
|
Parse.Promise.when([p1, p2])
|
||||||
|
.then(() => {
|
||||||
|
fail('one of the users should not have been created');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ensure that if people already have duplicate users, they can still sign up new users', done => {
|
||||||
|
reconfigureServer({})
|
||||||
|
// Remove existing data to clear out unique index
|
||||||
|
.then(TestUtils.destroyAllDataPermanently)
|
||||||
|
.then(() => {
|
||||||
|
let adapter = new MongoStorageAdapter({
|
||||||
|
uri: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase',
|
||||||
|
collectionPrefix: 'test_',
|
||||||
|
});
|
||||||
|
adapter.createObject('_User', { objectId: 'x', username: 'u' }, requiredUserFields)
|
||||||
|
.then(() => adapter.createObject('_User', { objectId: 'y', username: 'u' }, requiredUserFields))
|
||||||
|
.then(() => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setPassword('asdf');
|
||||||
|
user.setUsername('zxcv');
|
||||||
|
return user.signUp();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setPassword('asdf');
|
||||||
|
user.setUsername('u');
|
||||||
|
user.signUp()
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
fail(JSON.stringify(error));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
}, () => {
|
||||||
|
fail('destroyAllDataPermanently failed')
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ensure that if people already have duplicate emails, they can still sign up new users', done => {
|
||||||
|
reconfigureServer({})
|
||||||
|
// Wipe out existing database with unique index so we can create a duplicate user
|
||||||
|
.then(TestUtils.destroyAllDataPermanently)
|
||||||
|
.then(() => {
|
||||||
|
let adapter = new MongoStorageAdapter({
|
||||||
|
uri: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase',
|
||||||
|
collectionPrefix: 'test_',
|
||||||
|
});
|
||||||
|
adapter.createObject('_User', { objectId: 'x', email: 'a@b.c' }, requiredUserFields)
|
||||||
|
.then(() => adapter.createObject('_User', { objectId: 'y', email: 'a@b.c' }, requiredUserFields))
|
||||||
|
.then(() => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setPassword('asdf');
|
||||||
|
user.setUsername('qqq');
|
||||||
|
user.setEmail('unique@unique.unique');
|
||||||
|
return user.signUp();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setPassword('asdf');
|
||||||
|
user.setUsername('www');
|
||||||
|
user.setEmail('a@b.c');
|
||||||
|
user.signUp()
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
fail(JSON.stringify(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', done => {
|
||||||
|
let config = new Config('test');
|
||||||
|
config.database.adapter.ensureUniqueness('_User', ['randomField'], requiredUserFields)
|
||||||
|
.then(() => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setPassword('asdf');
|
||||||
|
user.setUsername('1');
|
||||||
|
user.setEmail('1@b.c');
|
||||||
|
user.set('randomField', 'a');
|
||||||
|
return user.signUp()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setPassword('asdf');
|
||||||
|
user.setUsername('2');
|
||||||
|
user.setEmail('2@b.c');
|
||||||
|
user.set('randomField', 'a');
|
||||||
|
return user.signUp()
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,8 +252,8 @@ describe('miscellaneous', function() {
|
|||||||
return Parse.User.logIn('test', 'moon-y');
|
return Parse.User.logIn('test', 'moon-y');
|
||||||
}).then((user) => {
|
}).then((user) => {
|
||||||
expect(user.get('foo')).toEqual(2);
|
expect(user.get('foo')).toEqual(2);
|
||||||
Parse.User.logOut();
|
Parse.User.logOut()
|
||||||
done();
|
.then(done);
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
fail(error);
|
fail(error);
|
||||||
done();
|
done();
|
||||||
@@ -202,14 +365,14 @@ describe('miscellaneous', function() {
|
|||||||
obj.set('foo', 'bar');
|
obj.set('foo', 'bar');
|
||||||
return obj.save();
|
return obj.save();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
var db = DatabaseAdapter.getDatabaseConnection(appId, 'test_');
|
let config = new Config(appId);
|
||||||
return db.adapter.find('TestObject', {}, { fields: {} }, {});
|
return config.database.adapter.find('TestObject', {}, { fields: {} }, {});
|
||||||
}).then((results) => {
|
}).then((results) => {
|
||||||
expect(results.length).toEqual(1);
|
expect(results.length).toEqual(1);
|
||||||
expect(results[0]['foo']).toEqual('bar');
|
expect(results[0]['foo']).toEqual('bar');
|
||||||
done();
|
done();
|
||||||
}).fail(err => {
|
}).fail(error => {
|
||||||
fail(err);
|
fail(JSON.stringify(error));
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -1119,27 +1282,6 @@ describe('miscellaneous', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fail when create duplicate value in unique field', (done) => {
|
|
||||||
let obj = new Parse.Object('UniqueField');
|
|
||||||
obj.set('unique', 'value');
|
|
||||||
obj.save().then(() => {
|
|
||||||
expect(obj.id).not.toBeUndefined();
|
|
||||||
let config = new Config('test');
|
|
||||||
return config.database.adapter.adaptiveCollection('UniqueField')
|
|
||||||
}).then(collection => {
|
|
||||||
return collection._mongoCollection.createIndex({ 'unique': 1 }, { unique: true })
|
|
||||||
}).then(() => {
|
|
||||||
let obj = new Parse.Object('UniqueField');
|
|
||||||
obj.set('unique', 'value');
|
|
||||||
return obj.save()
|
|
||||||
}).then(() => {
|
|
||||||
return Promise.reject();
|
|
||||||
}, error => {
|
|
||||||
expect(error.code === Parse.Error.DUPLICATE_VALUE);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('doesnt convert interior keys of objects that use special names', done => {
|
it('doesnt convert interior keys of objects that use special names', done => {
|
||||||
let obj = new Parse.Object('Obj');
|
let obj = new Parse.Object('Obj');
|
||||||
obj.set('val', { createdAt: 'a', updatedAt: 1 });
|
obj.set('val', { createdAt: 'a', updatedAt: 1 });
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
"use strict";
|
||||||
/* global describe, it, expect, fail, Parse */
|
/* global describe, it, expect, fail, Parse */
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var triggers = require('../src/triggers');
|
var triggers = require('../src/triggers');
|
||||||
@@ -13,7 +14,7 @@ var hookServerURL = "http://localhost:"+port;
|
|||||||
var app = express();
|
var app = express();
|
||||||
app.use(bodyParser.json({ 'type': '*/*' }))
|
app.use(bodyParser.json({ 'type': '*/*' }))
|
||||||
app.listen(12345);
|
app.listen(12345);
|
||||||
|
let AppCache = require('../src/cache').AppCache;
|
||||||
|
|
||||||
describe('Hooks', () => {
|
describe('Hooks', () => {
|
||||||
|
|
||||||
@@ -257,7 +258,7 @@ describe('Hooks', () => {
|
|||||||
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).toBeUndefined();
|
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).toBeUndefined();
|
||||||
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).toBeUndefined();
|
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).toBeUndefined();
|
||||||
}
|
}
|
||||||
const hooksController = new HooksController(Parse.applicationId);
|
const hooksController = new HooksController(Parse.applicationId, AppCache.get('test').databaseController);
|
||||||
return hooksController.load()
|
return hooksController.load()
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -347,28 +348,30 @@ describe('Hooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not pass X-Parse-Webhook-Key if not provided", (done) => {
|
it("should not pass X-Parse-Webhook-Key if not provided", (done) => {
|
||||||
setServerConfiguration(Object.assign({}, defaultConfiguration, { webhookKey: undefined }));
|
reconfigureServer({ webhookKey: undefined })
|
||||||
app.post("/ExpectingKeyAlso", function(req, res) {
|
.then(() => {
|
||||||
if (req.get('X-Parse-Webhook-Key') === 'hook') {
|
app.post("/ExpectingKeyAlso", function(req, res) {
|
||||||
res.json({success: "correct key provided"});
|
if (req.get('X-Parse-Webhook-Key') === 'hook') {
|
||||||
} else {
|
res.json({success: "correct key provided"});
|
||||||
res.json({error: "incorrect key provided"});
|
} else {
|
||||||
}
|
res.json({error: "incorrect key provided"});
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/ExpectingKeyAlso").then(function(){
|
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/ExpectingKeyAlso").then(function(){
|
||||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
fail("Should not fail creating a function");
|
fail("Should not fail creating a function");
|
||||||
done();
|
done();
|
||||||
}).then(function(res){
|
}).then(function(res){
|
||||||
fail("Should not succeed calling that function");
|
fail("Should not succeed calling that function");
|
||||||
done();
|
done();
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
expect(err.code).toBe(141);
|
expect(err.code).toBe(141);
|
||||||
expect(err.message).toEqual("incorrect key provided");
|
expect(err.message).toEqual("incorrect key provided");
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
let auth = require('../src/Auth');
|
let auth = require('../src/Auth');
|
||||||
let cache = require('../src/cache');
|
let cache = require('../src/cache');
|
||||||
let Config = require('../src/Config');
|
let Config = require('../src/Config');
|
||||||
let DatabaseAdapter = require('../src/DatabaseAdapter');
|
|
||||||
let Parse = require('parse/node').Parse;
|
let Parse = require('parse/node').Parse;
|
||||||
let rest = require('../src/rest');
|
let rest = require('../src/rest');
|
||||||
let request = require("request");
|
let request = require("request");
|
||||||
|
|
||||||
let config = new Config('test');
|
let config = new Config('test');
|
||||||
let database = DatabaseAdapter.getDatabaseConnection('test', 'test_');
|
let database = config.database;
|
||||||
let defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
|
let defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
|
||||||
|
|
||||||
const installationSchema = { fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation) };
|
const installationSchema = { fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation) };
|
||||||
|
|||||||
@@ -696,6 +696,7 @@ describe('Parse.Relation testing', () => {
|
|||||||
admins.first({ useMasterKey: true })
|
admins.first({ useMasterKey: true })
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
response.success(user);
|
||||||
done();
|
done();
|
||||||
} else {
|
} else {
|
||||||
fail('Should have found admin user, found nothing instead');
|
fail('Should have found admin user, found nothing instead');
|
||||||
|
|||||||
@@ -2384,35 +2384,32 @@ describe('Parse.User testing', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not revoke session tokens if the server is configures to not revoke session tokens', done => {
|
it('should not revoke session tokens if the server is configures to not revoke session tokens', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({ revokeSessionOnPasswordReset: false })
|
||||||
serverURL: 'http://localhost:8378/1',
|
.then(() => {
|
||||||
appId: 'test',
|
request.post({
|
||||||
masterKey: 'test',
|
url: 'http://localhost:8378/1/classes/_User',
|
||||||
revokeSessionOnPasswordReset: false,
|
headers: {
|
||||||
})
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
request.post({
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
url: 'http://localhost:8378/1/classes/_User',
|
},
|
||||||
headers: {
|
json: {authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
|
||||||
'X-Parse-Application-Id': Parse.applicationId,
|
}, (err, res, body) => {
|
||||||
'X-Parse-REST-API-Key': 'rest',
|
Parse.User.become(body.sessionToken)
|
||||||
},
|
.then(user => {
|
||||||
json: {authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
|
let obj = new Parse.Object('TestObject');
|
||||||
}, (err, res, body) => {
|
obj.setACL(new Parse.ACL(user));
|
||||||
Parse.User.become(body.sessionToken)
|
return obj.save()
|
||||||
.then(user => {
|
.then(() => {
|
||||||
let obj = new Parse.Object('TestObject');
|
// Change password, revoking session
|
||||||
obj.setACL(new Parse.ACL(user));
|
user.set('username', 'no longer anonymous');
|
||||||
return obj.save()
|
user.set('password', 'password');
|
||||||
.then(() => {
|
return user.save()
|
||||||
// Change password, revoking session
|
})
|
||||||
user.set('username', 'no longer anonymous');
|
.then(() => obj.fetch())
|
||||||
user.set('password', 'password');
|
// fetch should succeed as we still have our session token
|
||||||
return user.save()
|
.then(done, fail);
|
||||||
})
|
})
|
||||||
.then(() => obj.fetch())
|
});
|
||||||
// fetch should succeed as we still have our session token
|
|
||||||
.then(done, fail);
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ describe('Pointer Permissions', () => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle multiple writeUserFields', (done) => {
|
it('should handle multiple writeUserFields', done => {
|
||||||
let config = new Config(Parse.applicationId);
|
let config = new Config(Parse.applicationId);
|
||||||
let user = new Parse.User();
|
let user = new Parse.User();
|
||||||
let user2 = new Parse.User();
|
let user2 = new Parse.User();
|
||||||
@@ -207,27 +207,24 @@ describe('Pointer Permissions', () => {
|
|||||||
password: 'password'
|
password: 'password'
|
||||||
});
|
});
|
||||||
let obj = new Parse.Object('AnObject');
|
let obj = new Parse.Object('AnObject');
|
||||||
Parse.Object.saveAll([user, user2]).then(() => {
|
Parse.Object.saveAll([user, user2])
|
||||||
|
.then(() => {
|
||||||
obj.set('owner', user);
|
obj.set('owner', user);
|
||||||
obj.set('otherOwner', user2);
|
obj.set('otherOwner', user2);
|
||||||
return obj.save();
|
return obj.save();
|
||||||
}).then(() => {
|
})
|
||||||
return config.database.loadSchema().then((schema) => {
|
.then(() => config.database.loadSchema())
|
||||||
return schema.updateClass('AnObject', {}, {find: {"*": true},writeUserFields: ['owner', 'otherOwner']});
|
.then(schema => schema.updateClass('AnObject', {}, {find: {"*": true},writeUserFields: ['owner', 'otherOwner']}))
|
||||||
});
|
.then(() => Parse.User.logIn('user1', 'password'))
|
||||||
}).then(() => {
|
.then(() => obj.save({hello: 'fromUser1'}))
|
||||||
return Parse.User.logIn('user1', 'password');
|
.then(() => Parse.User.logIn('user2', 'password'))
|
||||||
}).then(() => {
|
.then(() => obj.save({hello: 'fromUser2'}))
|
||||||
return obj.save({hello: 'fromUser1'});
|
.then(() => Parse.User.logOut())
|
||||||
}).then(() => {
|
.then(() => {
|
||||||
return Parse.User.logIn('user2', 'password');
|
|
||||||
}).then(() => {
|
|
||||||
return obj.save({hello: 'fromUser2'});
|
|
||||||
}).then(() => {
|
|
||||||
Parse.User.logOut();
|
|
||||||
let q = new Parse.Query('AnObject');
|
let q = new Parse.Query('AnObject');
|
||||||
return q.first();
|
return q.first();
|
||||||
}).then((result) => {
|
})
|
||||||
|
.then(result => {
|
||||||
expect(result.get('hello')).toBe('fromUser2');
|
expect(result.get('hello')).toBe('fromUser2');
|
||||||
done();
|
done();
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
|
|||||||
@@ -2,43 +2,33 @@
|
|||||||
var request = require('request');
|
var request = require('request');
|
||||||
|
|
||||||
describe("public API", () => {
|
describe("public API", () => {
|
||||||
beforeEach(done => {
|
|
||||||
setServerConfiguration({
|
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
publicServerURL: 'http://localhost:8378/1'
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
it("should get invalid_link.html", (done) => {
|
it("should get invalid_link.html", (done) => {
|
||||||
request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse, body) => {
|
request('http://localhost:8378/1/apps/invalid_link.html', (err, httpResponse, body) => {
|
||||||
expect(httpResponse.statusCode).toBe(200);
|
expect(httpResponse.statusCode).toBe(200);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get choose_password", (done) => {
|
it("should get choose_password", (done) => {
|
||||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
|
reconfigureServer({
|
||||||
expect(httpResponse.statusCode).toBe(200);
|
appName: 'unused',
|
||||||
done();
|
publicServerURL: 'http://localhost:8378/1',
|
||||||
});
|
})
|
||||||
|
.then(() => {
|
||||||
|
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
|
||||||
|
expect(httpResponse.statusCode).toBe(200);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get verify_email_success.html", (done) => {
|
it("should get verify_email_success.html", (done) => {
|
||||||
request('http://localhost:8378/1/apps/verify_email_success.html', (err, httpResponse, body) => {
|
request('http://localhost:8378/1/apps/verify_email_success.html', (err, httpResponse, body) => {
|
||||||
expect(httpResponse.statusCode).toBe(200);
|
expect(httpResponse.statusCode).toBe(200);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get password_reset_success.html", (done) => {
|
it("should get password_reset_success.html", (done) => {
|
||||||
request('http://localhost:8378/1/apps/password_reset_success.html', (err, httpResponse, body) => {
|
request('http://localhost:8378/1/apps/password_reset_success.html', (err, httpResponse, body) => {
|
||||||
expect(httpResponse.statusCode).toBe(200);
|
expect(httpResponse.statusCode).toBe(200);
|
||||||
@@ -49,19 +39,8 @@ describe("public API", () => {
|
|||||||
|
|
||||||
describe("public API without publicServerURL", () => {
|
describe("public API without publicServerURL", () => {
|
||||||
beforeEach(done => {
|
beforeEach(done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({ appName: 'unused' })
|
||||||
serverURL: 'http://localhost:8378/1',
|
.then(done, fail);
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
})
|
})
|
||||||
it("should get 404 on verify_email", (done) => {
|
it("should get 404 on verify_email", (done) => {
|
||||||
request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse, body) => {
|
request('http://localhost:8378/1/apps/test/verify_email', (err, httpResponse, body) => {
|
||||||
@@ -69,14 +48,14 @@ describe("public API without publicServerURL", () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get 404 choose_password", (done) => {
|
it("should get 404 choose_password", (done) => {
|
||||||
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
|
request('http://localhost:8378/1/apps/choose_password?id=test', (err, httpResponse, body) => {
|
||||||
expect(httpResponse.statusCode).toBe(404);
|
expect(httpResponse.statusCode).toBe(404);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get 404 on request_password_reset", (done) => {
|
it("should get 404 on request_password_reset", (done) => {
|
||||||
request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse, body) => {
|
request('http://localhost:8378/1/apps/test/request_password_reset', (err, httpResponse, body) => {
|
||||||
expect(httpResponse.statusCode).toBe(404);
|
expect(httpResponse.statusCode).toBe(404);
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
var auth = require('../src/Auth');
|
var auth = require('../src/Auth');
|
||||||
var cache = require('../src/cache');
|
var cache = require('../src/cache');
|
||||||
var Config = require('../src/Config');
|
var Config = require('../src/Config');
|
||||||
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
var rest = require('../src/rest');
|
var rest = require('../src/rest');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
|
|
||||||
var config = new Config('test');
|
var config = new Config('test');
|
||||||
var database = DatabaseAdapter.getDatabaseConnection('test', 'test_');
|
let database = config.database;
|
||||||
|
|
||||||
describe('rest create', () => {
|
describe('rest create', () => {
|
||||||
it('handles _id', done => {
|
it('handles _id', done => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
'use strict'
|
||||||
// These tests check the "find" functionality of the REST API.
|
// These tests check the "find" functionality of the REST API.
|
||||||
var auth = require('../src/Auth');
|
var auth = require('../src/Auth');
|
||||||
var cache = require('../src/cache');
|
var cache = require('../src/cache');
|
||||||
@@ -7,10 +8,8 @@ var rest = require('../src/rest');
|
|||||||
var querystring = require('querystring');
|
var querystring = require('querystring');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
|
|
||||||
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
|
||||||
var database = DatabaseAdapter.getDatabaseConnection('test', 'test_');
|
|
||||||
|
|
||||||
var config = new Config('test');
|
var config = new Config('test');
|
||||||
|
let database = config.database;
|
||||||
var nobody = auth.nobody(config);
|
var nobody = auth.nobody(config);
|
||||||
|
|
||||||
describe('rest query', () => {
|
describe('rest query', () => {
|
||||||
|
|||||||
103
spec/Uniqueness.spec.js
Normal file
103
spec/Uniqueness.spec.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var request = require('request');
|
||||||
|
const Parse = require("parse/node");
|
||||||
|
let Config = require('../src/Config');
|
||||||
|
|
||||||
|
describe('Uniqueness', function() {
|
||||||
|
it('fail when create duplicate value in unique field', done => {
|
||||||
|
let obj = new Parse.Object('UniqueField');
|
||||||
|
obj.set('unique', 'value');
|
||||||
|
obj.save().then(() => {
|
||||||
|
expect(obj.id).not.toBeUndefined();
|
||||||
|
let config = new Config('test');
|
||||||
|
return config.database.adapter.ensureUniqueness('UniqueField', ['unique'], { fields: { unique: { __type: 'String' } } })
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
let obj = new Parse.Object('UniqueField');
|
||||||
|
obj.set('unique', 'value');
|
||||||
|
return obj.save()
|
||||||
|
}).then(() => {
|
||||||
|
fail('Saving duplicate field should have failed');
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unique indexing works on pointer fields', done => {
|
||||||
|
let obj = new Parse.Object('UniquePointer');
|
||||||
|
obj.save({ string: 'who cares' })
|
||||||
|
.then(() => obj.save({ ptr: obj }))
|
||||||
|
.then(() => {
|
||||||
|
let config = new Config('test');
|
||||||
|
return config.database.adapter.ensureUniqueness('UniquePointer', ['ptr'], { fields: {
|
||||||
|
string: { __type: 'String' },
|
||||||
|
ptr: { __type: 'Pointer', targetClass: 'UniquePointer' }
|
||||||
|
} });
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
let newObj = new Parse.Object('UniquePointer')
|
||||||
|
newObj.set('ptr', obj)
|
||||||
|
return newObj.save()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
fail('save should have failed due to duplicate value');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when attempting to ensure uniqueness of fields that are not currently unique', done => {
|
||||||
|
let o1 = new Parse.Object('UniqueFail');
|
||||||
|
o1.set('key', 'val');
|
||||||
|
let o2 = new Parse.Object('UniqueFail');
|
||||||
|
o2.set('key', 'val');
|
||||||
|
Parse.Object.saveAll([o1, o2])
|
||||||
|
.then(() => {
|
||||||
|
let config = new Config('test');
|
||||||
|
return config.database.adapter.ensureUniqueness('UniqueFail', ['key'], { fields: { key: { __type: 'String' } } });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can do compound uniqueness', done => {
|
||||||
|
let config = new Config('test');
|
||||||
|
config.database.adapter.ensureUniqueness('CompoundUnique', ['k1', 'k2'], { fields: { k1: { __type: 'String' }, k2: { __type: 'String' } } })
|
||||||
|
.then(() => {
|
||||||
|
let o1 = new Parse.Object('CompoundUnique');
|
||||||
|
o1.set('k1', 'v1');
|
||||||
|
o1.set('k2', 'v2');
|
||||||
|
return o1.save();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
let o2 = new Parse.Object('CompoundUnique');
|
||||||
|
o2.set('k1', 'v1');
|
||||||
|
o2.set('k2', 'not a dupe');
|
||||||
|
return o2.save();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
let o3 = new Parse.Object('CompoundUnique');
|
||||||
|
o3.set('k1', 'not a dupe');
|
||||||
|
o3.set('k2', 'v2');
|
||||||
|
return o3.save();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
let o4 = new Parse.Object('CompoundUnique');
|
||||||
|
o4.set('k1', 'v1');
|
||||||
|
o4.set('k2', 'v2');
|
||||||
|
return o4.save();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,17 +6,8 @@ let Config = require("../src/Config");
|
|||||||
|
|
||||||
describe("Custom Pages Configuration", () => {
|
describe("Custom Pages Configuration", () => {
|
||||||
it("should set the custom pages", (done) => {
|
it("should set the custom pages", (done) => {
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
customPages: {
|
customPages: {
|
||||||
invalidLink: "myInvalidLink",
|
invalidLink: "myInvalidLink",
|
||||||
verifyEmailSuccess: "myVerifyEmailSuccess",
|
verifyEmailSuccess: "myVerifyEmailSuccess",
|
||||||
@@ -24,17 +15,17 @@ describe("Custom Pages Configuration", () => {
|
|||||||
passwordResetSuccess: "myPasswordResetSuccess"
|
passwordResetSuccess: "myPasswordResetSuccess"
|
||||||
},
|
},
|
||||||
publicServerURL: "https://my.public.server.com/1"
|
publicServerURL: "https://my.public.server.com/1"
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
var config = new Config("test");
|
||||||
|
expect(config.invalidLinkURL).toEqual("myInvalidLink");
|
||||||
|
expect(config.verifyEmailSuccessURL).toEqual("myVerifyEmailSuccess");
|
||||||
|
expect(config.choosePasswordURL).toEqual("myChoosePassword");
|
||||||
|
expect(config.passwordResetSuccessURL).toEqual("myPasswordResetSuccess");
|
||||||
|
expect(config.verifyEmailURL).toEqual("https://my.public.server.com/1/apps/test/verify_email");
|
||||||
|
expect(config.requestResetPasswordURL).toEqual("https://my.public.server.com/1/apps/test/request_password_reset");
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
var config = new Config("test");
|
|
||||||
|
|
||||||
expect(config.invalidLinkURL).toEqual("myInvalidLink");
|
|
||||||
expect(config.verifyEmailSuccessURL).toEqual("myVerifyEmailSuccess");
|
|
||||||
expect(config.choosePasswordURL).toEqual("myChoosePassword");
|
|
||||||
expect(config.passwordResetSuccessURL).toEqual("myPasswordResetSuccess");
|
|
||||||
expect(config.verifyEmailURL).toEqual("https://my.public.server.com/1/apps/test/verify_email");
|
|
||||||
expect(config.requestResetPasswordURL).toEqual("https://my.public.server.com/1/apps/test/request_password_reset");
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,39 +36,32 @@ describe("Email Verification", () => {
|
|||||||
sendPasswordResetEmail: () => Promise.resolve(),
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
sendMail: () => Promise.resolve()
|
sendMail: () => Promise.resolve()
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
spyOn(emailAdapter, 'sendVerificationEmail');
|
.then(() => {
|
||||||
var user = new Parse.User();
|
spyOn(emailAdapter, 'sendVerificationEmail');
|
||||||
user.setPassword("asdf");
|
var user = new Parse.User();
|
||||||
user.setUsername("zxcv");
|
user.setPassword("asdf");
|
||||||
user.setEmail('testIfEnabled@parse.com');
|
user.setUsername("zxcv");
|
||||||
user.signUp(null, {
|
user.setEmail('testIfEnabled@parse.com');
|
||||||
success: function(user) {
|
user.signUp(null, {
|
||||||
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
|
success: function(user) {
|
||||||
user.fetch()
|
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
|
||||||
.then(() => {
|
user.fetch()
|
||||||
expect(user.get('emailVerified')).toEqual(false);
|
.then(() => {
|
||||||
|
expect(user.get('emailVerified')).toEqual(false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(userAgain, error) {
|
||||||
|
fail('Failed to save user');
|
||||||
done();
|
done();
|
||||||
});
|
}
|
||||||
},
|
});
|
||||||
error: function(userAgain, error) {
|
|
||||||
fail('Failed to save user');
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,38 +71,31 @@ describe("Email Verification", () => {
|
|||||||
sendPasswordResetEmail: () => Promise.resolve(),
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
sendMail: () => Promise.resolve()
|
sendMail: () => Promise.resolve()
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
spyOn(emailAdapter, 'sendVerificationEmail');
|
.then(() => {
|
||||||
var user = new Parse.User();
|
spyOn(emailAdapter, 'sendVerificationEmail');
|
||||||
user.setPassword("asdf");
|
var user = new Parse.User();
|
||||||
user.setUsername("zxcv");
|
user.setPassword("asdf");
|
||||||
user.signUp(null, {
|
user.setUsername("zxcv");
|
||||||
success: function(user) {
|
user.signUp(null, {
|
||||||
expect(emailAdapter.sendVerificationEmail).not.toHaveBeenCalled();
|
success: function(user) {
|
||||||
user.fetch()
|
expect(emailAdapter.sendVerificationEmail).not.toHaveBeenCalled();
|
||||||
.then(() => {
|
user.fetch()
|
||||||
expect(user.get('emailVerified')).toEqual(undefined);
|
.then(() => {
|
||||||
|
expect(user.get('emailVerified')).toEqual(undefined);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(userAgain, error) {
|
||||||
|
fail('Failed to save user');
|
||||||
done();
|
done();
|
||||||
});
|
}
|
||||||
},
|
});
|
||||||
error: function(userAgain, error) {
|
|
||||||
fail('Failed to save user');
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,47 +105,40 @@ describe("Email Verification", () => {
|
|||||||
sendPasswordResetEmail: () => Promise.resolve(),
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
sendMail: () => Promise.resolve()
|
sendMail: () => Promise.resolve()
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
spyOn(emailAdapter, 'sendVerificationEmail');
|
.then(() => {
|
||||||
var user = new Parse.User();
|
spyOn(emailAdapter, 'sendVerificationEmail');
|
||||||
user.setPassword("asdf");
|
var user = new Parse.User();
|
||||||
user.setUsername("zxcv");
|
user.setPassword("asdf");
|
||||||
user.signUp(null, {
|
user.setUsername("zxcv");
|
||||||
success: function(user) {
|
user.signUp(null, {
|
||||||
expect(emailAdapter.sendVerificationEmail).not.toHaveBeenCalled();
|
success: function(user) {
|
||||||
user.fetch()
|
expect(emailAdapter.sendVerificationEmail).not.toHaveBeenCalled();
|
||||||
.then((user) => {
|
user.fetch()
|
||||||
user.set("email", "testWhenUpdating@parse.com");
|
.then((user) => {
|
||||||
return user.save();
|
user.set("email", "testWhenUpdating@parse.com");
|
||||||
}).then((user) => {
|
return user.save();
|
||||||
return user.fetch();
|
}).then((user) => {
|
||||||
}).then(() => {
|
return user.fetch();
|
||||||
expect(user.get('emailVerified')).toEqual(false);
|
}).then(() => {
|
||||||
// Wait as on update email, we need to fetch the username
|
expect(user.get('emailVerified')).toEqual(false);
|
||||||
setTimeout(function(){
|
// Wait as on update email, we need to fetch the username
|
||||||
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
|
setTimeout(function(){
|
||||||
done();
|
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
|
||||||
}, 200);
|
done();
|
||||||
});
|
}, 200);
|
||||||
},
|
});
|
||||||
error: function(userAgain, error) {
|
},
|
||||||
fail('Failed to save user');
|
error: function(userAgain, error) {
|
||||||
done();
|
fail('Failed to save user');
|
||||||
}
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,51 +148,44 @@ describe("Email Verification", () => {
|
|||||||
sendPasswordResetEmail: () => Promise.resolve(),
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
sendMail: () => Promise.resolve()
|
sendMail: () => Promise.resolve()
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
spyOn(emailAdapter, 'sendVerificationEmail').and.callFake((options) => {
|
.then(() => {
|
||||||
expect(options.link).not.toBeNull();
|
spyOn(emailAdapter, 'sendVerificationEmail').and.callFake((options) => {
|
||||||
expect(options.link).not.toMatch(/token=undefined/);
|
expect(options.link).not.toBeNull();
|
||||||
Promise.resolve();
|
expect(options.link).not.toMatch(/token=undefined/);
|
||||||
});
|
Promise.resolve();
|
||||||
var user = new Parse.User();
|
});
|
||||||
user.setPassword("asdf");
|
var user = new Parse.User();
|
||||||
user.setUsername("zxcv");
|
user.setPassword("asdf");
|
||||||
user.signUp(null, {
|
user.setUsername("zxcv");
|
||||||
success: function(user) {
|
user.signUp(null, {
|
||||||
expect(emailAdapter.sendVerificationEmail).not.toHaveBeenCalled();
|
success: function(user) {
|
||||||
user.fetch()
|
expect(emailAdapter.sendVerificationEmail).not.toHaveBeenCalled();
|
||||||
.then((user) => {
|
user.fetch()
|
||||||
user.set("email", "testValidLinkWhenUpdating@parse.com");
|
.then((user) => {
|
||||||
return user.save();
|
user.set("email", "testValidLinkWhenUpdating@parse.com");
|
||||||
}).then((user) => {
|
return user.save();
|
||||||
return user.fetch();
|
}).then((user) => {
|
||||||
}).then(() => {
|
return user.fetch();
|
||||||
expect(user.get('emailVerified')).toEqual(false);
|
}).then(() => {
|
||||||
// Wait as on update email, we need to fetch the username
|
expect(user.get('emailVerified')).toEqual(false);
|
||||||
setTimeout(function(){
|
// Wait as on update email, we need to fetch the username
|
||||||
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
|
setTimeout(function(){
|
||||||
done();
|
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
|
||||||
}, 200);
|
done();
|
||||||
});
|
}, 200);
|
||||||
},
|
});
|
||||||
error: function(userAgain, error) {
|
},
|
||||||
fail('Failed to save user');
|
error: function(userAgain, error) {
|
||||||
done();
|
fail('Failed to save user');
|
||||||
}
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -242,60 +205,44 @@ describe("Email Verification", () => {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'My Cool App',
|
appName: 'My Cool App',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
var user = new Parse.User();
|
.then(() => {
|
||||||
user.setPassword("asdf");
|
var user = new Parse.User();
|
||||||
user.setUsername("zxcv");
|
user.setPassword("asdf");
|
||||||
user.set("email", "testSendSimpleAdapter@parse.com");
|
user.setUsername("zxcv");
|
||||||
user.signUp(null, {
|
user.set("email", "testSendSimpleAdapter@parse.com");
|
||||||
success: function(user) {
|
user.signUp(null, {
|
||||||
expect(calls).toBe(1);
|
success: function(user) {
|
||||||
user.fetch()
|
expect(calls).toBe(1);
|
||||||
.then((user) => {
|
user.fetch()
|
||||||
return user.save();
|
.then((user) => {
|
||||||
}).then((user) => {
|
return user.save();
|
||||||
return Parse.User.requestPasswordReset("testSendSimpleAdapter@parse.com").catch((err) => {
|
}).then((user) => {
|
||||||
fail('Should not fail requesting a password');
|
return Parse.User.requestPasswordReset("testSendSimpleAdapter@parse.com").catch((err) => {
|
||||||
|
fail('Should not fail requesting a password');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
expect(calls).toBe(2);
|
||||||
done();
|
done();
|
||||||
})
|
});
|
||||||
}).then(() => {
|
},
|
||||||
expect(calls).toBe(2);
|
error: function(userAgain, error) {
|
||||||
|
fail('Failed to save user');
|
||||||
done();
|
done();
|
||||||
});
|
}
|
||||||
},
|
});
|
||||||
error: function(userAgain, error) {
|
|
||||||
fail('Failed to save user');
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails if you include an emailAdapter, set verifyUserEmails to false, dont set a publicServerURL, and try to send a password reset email (regression test for #1649)', done => {
|
it('fails if you include an emailAdapter, set verifyUserEmails to false, dont set a publicServerURL, and try to send a password reset email (regression test for #1649)', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: false,
|
verifyUserEmails: false,
|
||||||
emailAdapter: MockEmailAdapterWithOptions({
|
emailAdapter: MockEmailAdapterWithOptions({
|
||||||
fromAddress: 'parse@example.com',
|
fromAddress: 'parse@example.com',
|
||||||
@@ -303,20 +250,21 @@ describe("Email Verification", () => {
|
|||||||
domain: 'd',
|
domain: 'd',
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
let user = new Parse.User();
|
let user = new Parse.User();
|
||||||
user.setPassword("asdf");
|
user.setPassword("asdf");
|
||||||
user.setUsername("zxcv");
|
user.setUsername("zxcv");
|
||||||
user.set("email", "testInvalidConfig@parse.com");
|
user.set("email", "testInvalidConfig@parse.com");
|
||||||
user.signUp(null)
|
user.signUp(null)
|
||||||
.then(user => Parse.User.requestPasswordReset("testInvalidConfig@parse.com"))
|
.then(user => Parse.User.requestPasswordReset("testInvalidConfig@parse.com"))
|
||||||
.then(result => {
|
.then(result => {
|
||||||
console.log(result);
|
console.log(result);
|
||||||
fail('sending password reset email should not have succeeded');
|
fail('sending password reset email should not have succeeded');
|
||||||
done();
|
done();
|
||||||
}, error => {
|
}, error => {
|
||||||
expect(error.message).toEqual('An appName, publicServerURL, and emailAdapter are required for password reset functionality.')
|
expect(error.message).toEqual('An appName, publicServerURL, and emailAdapter are required for password reset functionality.')
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -326,37 +274,30 @@ describe("Email Verification", () => {
|
|||||||
sendPasswordResetEmail: () => Promise.resolve(),
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
sendMail: () => Promise.resolve()
|
sendMail: () => Promise.resolve()
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: false,
|
verifyUserEmails: false,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
});
|
})
|
||||||
spyOn(emailAdapter, 'sendVerificationEmail');
|
.then(() => {
|
||||||
var user = new Parse.User();
|
spyOn(emailAdapter, 'sendVerificationEmail');
|
||||||
user.setPassword("asdf");
|
var user = new Parse.User();
|
||||||
user.setUsername("zxcv");
|
user.setPassword("asdf");
|
||||||
user.signUp(null, {
|
user.setUsername("zxcv");
|
||||||
success: function(user) {
|
user.signUp(null, {
|
||||||
user.fetch()
|
success: function(user) {
|
||||||
.then(() => {
|
user.fetch()
|
||||||
expect(emailAdapter.sendVerificationEmail.calls.count()).toEqual(0);
|
.then(() => {
|
||||||
expect(user.get('emailVerified')).toEqual(undefined);
|
expect(emailAdapter.sendVerificationEmail.calls.count()).toEqual(0);
|
||||||
|
expect(user.get('emailVerified')).toEqual(undefined);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(userAgain, error) {
|
||||||
|
fail('Failed to save user');
|
||||||
done();
|
done();
|
||||||
});
|
}
|
||||||
},
|
});
|
||||||
error: function(userAgain, error) {
|
|
||||||
fail('Failed to save user');
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -370,31 +311,24 @@ describe("Email Verification", () => {
|
|||||||
sendPasswordResetEmail: () => Promise.resolve(),
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
sendMail: () => {}
|
sendMail: () => {}
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'emailing app',
|
appName: 'emailing app',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
var user = new Parse.User();
|
.then(() => {
|
||||||
user.setPassword("asdf");
|
var user = new Parse.User();
|
||||||
user.setUsername("zxcv");
|
user.setPassword("asdf");
|
||||||
user.set('email', 'user@parse.com');
|
user.setUsername("zxcv");
|
||||||
user.signUp(null, {
|
user.set('email', 'user@parse.com');
|
||||||
success: () => {},
|
user.signUp(null, {
|
||||||
error: function(userAgain, error) {
|
success: () => {},
|
||||||
fail('Failed to save user');
|
error: function(userAgain, error) {
|
||||||
done();
|
fail('Failed to save user');
|
||||||
}
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -421,39 +355,23 @@ describe("Email Verification", () => {
|
|||||||
sendPasswordResetEmail: () => Promise.resolve(),
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
sendMail: () => {}
|
sendMail: () => {}
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'emailing app',
|
appName: 'emailing app',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
user.setPassword("asdf");
|
||||||
|
user.setUsername("user");
|
||||||
|
user.set('email', 'user@parse.com');
|
||||||
|
user.signUp();
|
||||||
});
|
});
|
||||||
user.setPassword("asdf");
|
|
||||||
user.setUsername("user");
|
|
||||||
user.set('email', 'user@parse.com');
|
|
||||||
user.signUp();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirects you to invalid link if you try to verify email incorrecly', done => {
|
it('redirects you to invalid link if you try to verify email incorrecly', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'emailing app',
|
appName: 'emailing app',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: {
|
emailAdapter: {
|
||||||
sendVerificationEmail: () => Promise.resolve(),
|
sendVerificationEmail: () => Promise.resolve(),
|
||||||
@@ -461,28 +379,21 @@ describe("Email Verification", () => {
|
|||||||
sendMail: () => {}
|
sendMail: () => {}
|
||||||
},
|
},
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
request.get('http://localhost:8378/1/apps/test/verify_email', {
|
.then(() => {
|
||||||
followRedirect: false,
|
request.get('http://localhost:8378/1/apps/test/verify_email', {
|
||||||
}, (error, response, body) => {
|
followRedirect: false,
|
||||||
expect(response.statusCode).toEqual(302);
|
}, (error, response, body) => {
|
||||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
expect(response.statusCode).toEqual(302);
|
||||||
done()
|
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
||||||
|
done()
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirects you to invalid link if you try to validate a nonexistant users email', done => {
|
it('redirects you to invalid link if you try to validate a nonexistant users email', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'emailing app',
|
appName: 'emailing app',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: {
|
emailAdapter: {
|
||||||
sendVerificationEmail: () => Promise.resolve(),
|
sendVerificationEmail: () => Promise.resolve(),
|
||||||
@@ -490,13 +401,15 @@ describe("Email Verification", () => {
|
|||||||
sendMail: () => {}
|
sendMail: () => {}
|
||||||
},
|
},
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
request.get('http://localhost:8378/1/apps/test/verify_email?token=asdfasdf&username=sadfasga', {
|
.then(() => {
|
||||||
followRedirect: false,
|
request.get('http://localhost:8378/1/apps/test/verify_email?token=asdfasdf&username=sadfasga', {
|
||||||
}, (error, response, body) => {
|
followRedirect: false,
|
||||||
expect(response.statusCode).toEqual(302);
|
}, (error, response, body) => {
|
||||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
expect(response.statusCode).toEqual(302);
|
||||||
done();
|
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -519,30 +432,23 @@ describe("Email Verification", () => {
|
|||||||
sendPasswordResetEmail: () => Promise.resolve(),
|
sendPasswordResetEmail: () => Promise.resolve(),
|
||||||
sendMail: () => {}
|
sendMail: () => {}
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'emailing app',
|
appName: 'emailing app',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
user.setPassword("asdf");
|
.then(() => {
|
||||||
user.setUsername("zxcv");
|
user.setPassword("asdf");
|
||||||
user.set('email', 'user@parse.com');
|
user.setUsername("zxcv");
|
||||||
user.signUp(null, {
|
user.set('email', 'user@parse.com');
|
||||||
success: () => {},
|
user.signUp(null, {
|
||||||
error: function(userAgain, error) {
|
success: () => {},
|
||||||
fail('Failed to save user');
|
error: function(userAgain, error) {
|
||||||
done();
|
fail('Failed to save user');
|
||||||
}
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -570,47 +476,31 @@ describe("Password Reset", () => {
|
|||||||
},
|
},
|
||||||
sendMail: () => {}
|
sendMail: () => {}
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'emailing app',
|
appName: 'emailing app',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
user.setPassword("asdf");
|
.then(() => {
|
||||||
user.setUsername("zxcv+zxcv");
|
user.setPassword("asdf");
|
||||||
user.set('email', 'user@parse.com');
|
user.setUsername("zxcv+zxcv");
|
||||||
user.signUp().then(() => {
|
user.set('email', 'user@parse.com');
|
||||||
Parse.User.requestPasswordReset('user@parse.com', {
|
user.signUp().then(() => {
|
||||||
error: (err) => {
|
Parse.User.requestPasswordReset('user@parse.com', {
|
||||||
console.error(err);
|
error: (err) => {
|
||||||
fail("Should not fail requesting a password");
|
console.error(err);
|
||||||
done();
|
fail("Should not fail requesting a password");
|
||||||
}
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirects you to invalid link if you try to request password for a nonexistant users email', done => {
|
it('redirects you to invalid link if you try to request password for a nonexistant users email', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'emailing app',
|
appName: 'emailing app',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: {
|
emailAdapter: {
|
||||||
sendVerificationEmail: () => Promise.resolve(),
|
sendVerificationEmail: () => Promise.resolve(),
|
||||||
@@ -618,13 +508,15 @@ describe("Password Reset", () => {
|
|||||||
sendMail: () => {}
|
sendMail: () => {}
|
||||||
},
|
},
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
request.get('http://localhost:8378/1/apps/test/request_password_reset?token=asdfasdf&username=sadfasga', {
|
.then(() => {
|
||||||
followRedirect: false,
|
request.get('http://localhost:8378/1/apps/test/request_password_reset?token=asdfasdf&username=sadfasga', {
|
||||||
}, (error, response, body) => {
|
followRedirect: false,
|
||||||
expect(response.statusCode).toEqual(302);
|
}, (error, response, body) => {
|
||||||
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
expect(response.statusCode).toEqual(302);
|
||||||
done();
|
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -688,33 +580,25 @@ describe("Password Reset", () => {
|
|||||||
},
|
},
|
||||||
sendMail: () => {}
|
sendMail: () => {}
|
||||||
}
|
}
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'emailing app',
|
appName: 'emailing app',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: emailAdapter,
|
emailAdapter: emailAdapter,
|
||||||
publicServerURL: "http://localhost:8378/1"
|
publicServerURL: "http://localhost:8378/1"
|
||||||
});
|
})
|
||||||
user.setPassword("asdf");
|
.then(() => {
|
||||||
user.setUsername("zxcv");
|
user.setPassword("asdf");
|
||||||
user.set('email', 'user@parse.com');
|
user.setUsername("zxcv");
|
||||||
user.signUp().then(() => {
|
user.set('email', 'user@parse.com');
|
||||||
Parse.User.requestPasswordReset('user@parse.com', {
|
user.signUp().then(() => {
|
||||||
error: (err) => {
|
Parse.User.requestPasswordReset('user@parse.com', {
|
||||||
console.error(err);
|
error: (err) => {
|
||||||
fail("Should not fail");
|
console.error(err);
|
||||||
done();
|
fail("Should not fail");
|
||||||
}
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
126
spec/helper.js
126
spec/helper.js
@@ -1,6 +1,7 @@
|
|||||||
|
"use strict"
|
||||||
// Sets up a Parse API server for testing.
|
// Sets up a Parse API server for testing.
|
||||||
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = process.env.PARSE_SERVER_TEST_TIMEOUT || 3000;
|
||||||
|
|
||||||
var cache = require('../src/cache').default;
|
var cache = require('../src/cache').default;
|
||||||
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||||
@@ -10,13 +11,23 @@ var ParseServer = require('../src/index').ParseServer;
|
|||||||
var path = require('path');
|
var path = require('path');
|
||||||
var TestUtils = require('../src/index').TestUtils;
|
var TestUtils = require('../src/index').TestUtils;
|
||||||
var MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
var MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||||
|
const GridStoreAdapter = require('../src/Adapters/Files/GridStoreAdapter').GridStoreAdapter;
|
||||||
|
|
||||||
|
|
||||||
var databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
|
||||||
var port = 8378;
|
var port = 8378;
|
||||||
|
|
||||||
|
let mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||||
|
let mongoAdapter = new MongoStorageAdapter({
|
||||||
|
uri: mongoURI,
|
||||||
|
collectionPrefix: 'test_',
|
||||||
|
})
|
||||||
|
|
||||||
|
let gridStoreAdapter = new GridStoreAdapter(mongoURI);
|
||||||
|
|
||||||
// Default server configuration for tests.
|
// Default server configuration for tests.
|
||||||
var defaultConfiguration = {
|
var defaultConfiguration = {
|
||||||
databaseURI: databaseURI,
|
databaseAdapter: mongoAdapter,
|
||||||
|
filesAdapter: gridStoreAdapter,
|
||||||
serverURL: 'http://localhost:' + port + '/1',
|
serverURL: 'http://localhost:' + port + '/1',
|
||||||
appId: 'test',
|
appId: 'test',
|
||||||
javascriptKey: 'test',
|
javascriptKey: 'test',
|
||||||
@@ -25,14 +36,13 @@ var defaultConfiguration = {
|
|||||||
restAPIKey: 'rest',
|
restAPIKey: 'rest',
|
||||||
webhookKey: 'hook',
|
webhookKey: 'hook',
|
||||||
masterKey: 'test',
|
masterKey: 'test',
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
fileKey: 'test',
|
||||||
push: {
|
push: {
|
||||||
'ios': {
|
'ios': {
|
||||||
cert: 'prodCert.pem',
|
cert: 'prodCert.pem',
|
||||||
key: 'prodKey.pem',
|
key: 'prodKey.pem',
|
||||||
production: true,
|
production: true,
|
||||||
bundleId: 'bundleId'
|
bundleId: 'bundleId',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
oauth: { // Override the facebook provider
|
oauth: { // Override the facebook provider
|
||||||
@@ -43,33 +53,45 @@ var defaultConfiguration = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let openConnections = {};
|
||||||
|
|
||||||
// Set up a default API server for testing with default configuration.
|
// Set up a default API server for testing with default configuration.
|
||||||
var api = new ParseServer(defaultConfiguration);
|
var api = new ParseServer(defaultConfiguration);
|
||||||
var app = express();
|
var app = express();
|
||||||
app.use('/1', api);
|
app.use('/1', api);
|
||||||
|
|
||||||
var server = app.listen(port);
|
var server = app.listen(port);
|
||||||
|
server.on('connection', connection => {
|
||||||
|
let key = `${connection.remoteAddress}:${connection.remotePort}`;
|
||||||
|
openConnections[key] = connection;
|
||||||
|
connection.on('close', () => { delete openConnections[key] });
|
||||||
|
});
|
||||||
|
|
||||||
// Prevent reinitializing the server from clobbering Cloud Code
|
|
||||||
delete defaultConfiguration.cloud;
|
|
||||||
|
|
||||||
var currentConfiguration;
|
|
||||||
// Allows testing specific configurations of Parse Server
|
// Allows testing specific configurations of Parse Server
|
||||||
const setServerConfiguration = configuration => {
|
const reconfigureServer = changedConfiguration => {
|
||||||
// the configuration hasn't changed
|
return new Promise((resolve, reject) => {
|
||||||
if (configuration === currentConfiguration) {
|
server.close(() => {
|
||||||
return;
|
try {
|
||||||
}
|
let newConfiguration = Object.assign({}, defaultConfiguration, changedConfiguration, {
|
||||||
DatabaseAdapter.clearDatabaseSettings();
|
__indexBuildCompletionCallbackForTests: indexBuildPromise => indexBuildPromise.then(resolve, reject)
|
||||||
currentConfiguration = configuration;
|
});
|
||||||
server.close();
|
cache.clear();
|
||||||
cache.clear();
|
app = express();
|
||||||
app = express();
|
api = new ParseServer(newConfiguration);
|
||||||
api = new ParseServer(configuration);
|
app.use('/1', api);
|
||||||
app.use('/1', api);
|
|
||||||
server = app.listen(port);
|
|
||||||
};
|
|
||||||
|
|
||||||
var restoreServerConfiguration = () => setServerConfiguration(defaultConfiguration);
|
server = app.listen(port);
|
||||||
|
server.on('connection', connection => {
|
||||||
|
let key = `${connection.remoteAddress}:${connection.remotePort}`;
|
||||||
|
openConnections[key] = connection;
|
||||||
|
connection.on('close', () => { delete openConnections[key] });
|
||||||
|
});
|
||||||
|
} catch(error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Set up a Parse client to talk to our test API server
|
// Set up a Parse client to talk to our test API server
|
||||||
var Parse = require('parse/node');
|
var Parse = require('parse/node');
|
||||||
@@ -79,19 +101,35 @@ Parse.serverURL = 'http://localhost:' + port + '/1';
|
|||||||
// TODO: update tests to work in an A+ way
|
// TODO: update tests to work in an A+ way
|
||||||
Parse.Promise.disableAPlusCompliant();
|
Parse.Promise.disableAPlusCompliant();
|
||||||
|
|
||||||
beforeEach(function(done) {
|
beforeEach(done => {
|
||||||
restoreServerConfiguration();
|
try {
|
||||||
Parse.initialize('test', 'test', 'test');
|
Parse.User.enableUnsafeCurrentUser();
|
||||||
Parse.serverURL = 'http://localhost:' + port + '/1';
|
} catch (error) {
|
||||||
Parse.User.enableUnsafeCurrentUser();
|
if (error !== 'You need to call Parse.initialize before using Parse.') {
|
||||||
return TestUtils.destroyAllDataPermanently().then(done, fail);
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TestUtils.destroyAllDataPermanently()
|
||||||
|
.catch(error => {
|
||||||
|
// For tests that connect to their own mongo, there won't be any data to delete.
|
||||||
|
if (error.message === 'ns not found' || error.message.startsWith('connect ECONNREFUSED')) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
fail(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(reconfigureServer)
|
||||||
|
.then(() => {
|
||||||
|
Parse.initialize('test', 'test', 'test');
|
||||||
|
Parse.serverURL = 'http://localhost:' + port + '/1';
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
fail(JSON.stringify(error));
|
||||||
|
done();
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
var mongoAdapter = new MongoStorageAdapter({
|
|
||||||
collectionPrefix: defaultConfiguration.collectionPrefix,
|
|
||||||
uri: databaseURI,
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function(done) {
|
afterEach(function(done) {
|
||||||
Parse.Cloud._removeAllHooks();
|
Parse.Cloud._removeAllHooks();
|
||||||
mongoAdapter.getAllSchemas()
|
mongoAdapter.getAllSchemas()
|
||||||
@@ -111,11 +149,13 @@ afterEach(function(done) {
|
|||||||
})
|
})
|
||||||
.then(() => Parse.User.logOut())
|
.then(() => Parse.User.logOut())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return TestUtils.destroyAllDataPermanently();
|
if (Object.keys(openConnections).length > 0) {
|
||||||
}).then(() => {
|
fail('There were open connections to the server left after the test finished');
|
||||||
|
}
|
||||||
done();
|
done();
|
||||||
}, (error) => {
|
})
|
||||||
console.log('error in clearData', error);
|
.catch(error => {
|
||||||
|
fail(JSON.stringify(error));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -243,14 +283,16 @@ function mockFacebook() {
|
|||||||
facebook.validateAuthData = function(authData) {
|
facebook.validateAuthData = function(authData) {
|
||||||
if (authData.id === '8675309' && authData.access_token === 'jenny') {
|
if (authData.id === '8675309' && authData.access_token === 'jenny') {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
} else {
|
||||||
|
throw undefined;
|
||||||
}
|
}
|
||||||
return Promise.reject();
|
|
||||||
};
|
};
|
||||||
facebook.validateAppId = function(appId, authData) {
|
facebook.validateAppId = function(appId, authData) {
|
||||||
if (authData.access_token === 'jenny') {
|
if (authData.access_token === 'jenny') {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
} else {
|
||||||
|
throw undefined;
|
||||||
}
|
}
|
||||||
return Promise.reject();
|
|
||||||
};
|
};
|
||||||
return facebook;
|
return facebook;
|
||||||
}
|
}
|
||||||
@@ -272,7 +314,7 @@ global.expectError = expectError;
|
|||||||
global.arrayContains = arrayContains;
|
global.arrayContains = arrayContains;
|
||||||
global.jequal = jequal;
|
global.jequal = jequal;
|
||||||
global.range = range;
|
global.range = range;
|
||||||
global.setServerConfiguration = setServerConfiguration;
|
global.reconfigureServer = reconfigureServer;
|
||||||
global.defaultConfiguration = defaultConfiguration;
|
global.defaultConfiguration = defaultConfiguration;
|
||||||
|
|
||||||
// LiveQuery test setting
|
// LiveQuery test setting
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
"use strict"
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var parseServerPackage = require('../package.json');
|
var parseServerPackage = require('../package.json');
|
||||||
var MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions');
|
var MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions');
|
||||||
@@ -5,12 +6,23 @@ var ParseServer = require("../src/index");
|
|||||||
var Config = require('../src/Config');
|
var Config = require('../src/Config');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
|
|
||||||
|
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||||
|
|
||||||
describe('server', () => {
|
describe('server', () => {
|
||||||
it('requires a master key and app id', done => {
|
it('requires a master key and app id', done => {
|
||||||
expect(setServerConfiguration.bind(undefined, { })).toThrow('You must provide an appId!');
|
reconfigureServer({ appId: undefined })
|
||||||
expect(setServerConfiguration.bind(undefined, { appId: 'myId' })).toThrow('You must provide a masterKey!');
|
.catch(error => {
|
||||||
expect(setServerConfiguration.bind(undefined, { appId: 'myId', masterKey: 'mk' })).toThrow('You must provide a serverURL!');
|
expect(error).toEqual('You must provide an appId!');
|
||||||
done();
|
return reconfigureServer({ masterKey: undefined });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error).toEqual('You must provide a masterKey!');
|
||||||
|
return reconfigureServer({ serverURL: undefined });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
expect(error).toEqual('You must provide a serverURL!');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('support http basic authentication with masterkey', done => {
|
it('support http basic authentication with masterkey', done => {
|
||||||
@@ -38,47 +50,29 @@ describe('server', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fails if database is unreachable', done => {
|
it('fails if database is unreachable', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({ databaseAdapter: new MongoStorageAdapter({ uri: 'mongodb://fake:fake@localhost:43605/drew3' }) })
|
||||||
databaseURI: 'mongodb://fake:fake@ds043605.mongolab.com:43605/drew3',
|
.catch(() => {
|
||||||
serverURL: 'http://localhost:8378/1',
|
//Need to use rest api because saving via JS SDK results in fail() not getting called
|
||||||
appId: 'test',
|
request.post({
|
||||||
javascriptKey: 'test',
|
url: 'http://localhost:8378/1/classes/NewClass',
|
||||||
dotNetKey: 'windows',
|
headers: {
|
||||||
clientKey: 'client',
|
'X-Parse-Application-Id': 'test',
|
||||||
restAPIKey: 'rest',
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
masterKey: 'test',
|
},
|
||||||
collectionPrefix: 'test_',
|
body: {},
|
||||||
fileKey: 'test',
|
json: true,
|
||||||
});
|
}, (error, response, body) => {
|
||||||
//Need to use rest api because saving via JS SDK results in fail() not getting called
|
expect(response.statusCode).toEqual(500);
|
||||||
request.post({
|
expect(body.code).toEqual(1);
|
||||||
url: 'http://localhost:8378/1/classes/NewClass',
|
expect(body.message).toEqual('Internal server error.');
|
||||||
headers: {
|
done();
|
||||||
'X-Parse-Application-Id': 'test',
|
});
|
||||||
'X-Parse-REST-API-Key': 'rest',
|
|
||||||
},
|
|
||||||
body: {},
|
|
||||||
json: true,
|
|
||||||
}, (error, response, body) => {
|
|
||||||
expect(response.statusCode).toEqual(500);
|
|
||||||
expect(body.code).toEqual(1);
|
|
||||||
expect(body.message).toEqual('Internal server error.');
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can load email adapter via object', done => {
|
it('can load email adapter via object', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: MockEmailAdapterWithOptions({
|
emailAdapter: MockEmailAdapterWithOptions({
|
||||||
fromAddress: 'parse@example.com',
|
fromAddress: 'parse@example.com',
|
||||||
@@ -86,22 +80,12 @@ describe('server', () => {
|
|||||||
domain: 'd',
|
domain: 'd',
|
||||||
}),
|
}),
|
||||||
publicServerURL: 'http://localhost:8378/1'
|
publicServerURL: 'http://localhost:8378/1'
|
||||||
});
|
}).then(done, fail);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can load email adapter via class', done => {
|
it('can load email adapter via class', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: {
|
emailAdapter: {
|
||||||
class: MockEmailAdapterWithOptions,
|
class: MockEmailAdapterWithOptions,
|
||||||
@@ -112,22 +96,12 @@ describe('server', () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
publicServerURL: 'http://localhost:8378/1'
|
publicServerURL: 'http://localhost:8378/1'
|
||||||
});
|
}).then(done, fail);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can load email adapter via module name', done => {
|
it('can load email adapter via module name', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: {
|
emailAdapter: {
|
||||||
module: 'parse-server-simple-mailgun-adapter',
|
module: 'parse-server-simple-mailgun-adapter',
|
||||||
@@ -138,41 +112,25 @@ describe('server', () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
publicServerURL: 'http://localhost:8378/1'
|
publicServerURL: 'http://localhost:8378/1'
|
||||||
});
|
}).then(done, fail);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can load email adapter via only module name', done => {
|
it('can load email adapter via only module name', done => {
|
||||||
expect(() => setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: 'parse-server-simple-mailgun-adapter',
|
emailAdapter: 'parse-server-simple-mailgun-adapter',
|
||||||
publicServerURL: 'http://localhost:8378/1'
|
publicServerURL: 'http://localhost:8378/1'
|
||||||
})).toThrow('SimpleMailgunAdapter requires an API Key, domain, and fromAddress.');
|
})
|
||||||
done();
|
.catch(error => {
|
||||||
|
expect(error).toEqual('SimpleMailgunAdapter requires an API Key, domain, and fromAddress.');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws if you initialize email adapter incorrecly', done => {
|
it('throws if you initialize email adapter incorrecly', done => {
|
||||||
expect(() => setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
appName: 'unused',
|
||||||
javascriptKey: 'test',
|
|
||||||
dotNetKey: 'windows',
|
|
||||||
clientKey: 'client',
|
|
||||||
restAPIKey: 'rest',
|
|
||||||
masterKey: 'test',
|
|
||||||
collectionPrefix: 'test_',
|
|
||||||
fileKey: 'test',
|
|
||||||
verifyUserEmails: true,
|
verifyUserEmails: true,
|
||||||
emailAdapter: {
|
emailAdapter: {
|
||||||
module: 'parse-server-simple-mailgun-adapter',
|
module: 'parse-server-simple-mailgun-adapter',
|
||||||
@@ -181,8 +139,11 @@ describe('server', () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
publicServerURL: 'http://localhost:8378/1'
|
publicServerURL: 'http://localhost:8378/1'
|
||||||
})).toThrow('SimpleMailgunAdapter requires an API Key, domain, and fromAddress.');
|
})
|
||||||
done();
|
.catch(error => {
|
||||||
|
expect(error).toEqual('SimpleMailgunAdapter requires an API Key, domain, and fromAddress.');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can report the server version', done => {
|
it('can report the server version', done => {
|
||||||
@@ -199,62 +160,71 @@ describe('server', () => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can create a parse-server', done => {
|
it('can create a parse-server v1', done => {
|
||||||
var parseServer = new ParseServer.default({
|
var parseServer = new ParseServer.default(Object.assign({},
|
||||||
|
defaultConfiguration, {
|
||||||
appId: "aTestApp",
|
appId: "aTestApp",
|
||||||
masterKey: "aTestMasterKey",
|
masterKey: "aTestMasterKey",
|
||||||
serverURL: "http://localhost:12666/parse",
|
serverURL: "http://localhost:12666/parse",
|
||||||
databaseURI: 'mongodb://localhost:27017/aTestApp'
|
__indexBuildCompletionCallbackForTests: promise => {
|
||||||
});
|
promise
|
||||||
|
.then(() => {
|
||||||
|
expect(Parse.applicationId).toEqual("aTestApp");
|
||||||
|
var app = express();
|
||||||
|
app.use('/parse', parseServer.app);
|
||||||
|
|
||||||
expect(Parse.applicationId).toEqual("aTestApp");
|
var server = app.listen(12666);
|
||||||
var app = express();
|
var obj = new Parse.Object("AnObject");
|
||||||
app.use('/parse', parseServer.app);
|
var objId;
|
||||||
|
obj.save().then((obj) => {
|
||||||
var server = app.listen(12666);
|
objId = obj.id;
|
||||||
var obj = new Parse.Object("AnObject");
|
var q = new Parse.Query("AnObject");
|
||||||
var objId;
|
return q.first();
|
||||||
obj.save().then((obj) => {
|
}).then((obj) => {
|
||||||
objId = obj.id;
|
expect(obj.id).toEqual(objId);
|
||||||
var q = new Parse.Query("AnObject");
|
server.close(done);
|
||||||
return q.first();
|
}).fail((err) => {
|
||||||
}).then((obj) => {
|
server.close(done);
|
||||||
expect(obj.id).toEqual(objId);
|
})
|
||||||
server.close();
|
});
|
||||||
done();
|
}})
|
||||||
}).fail((err) => {
|
);
|
||||||
server.close();
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can create a parse-server', done => {
|
it('can create a parse-server v2', done => {
|
||||||
var parseServer = ParseServer.ParseServer({
|
let objId;
|
||||||
|
let server
|
||||||
|
let parseServer = ParseServer.ParseServer(Object.assign({},
|
||||||
|
defaultConfiguration, {
|
||||||
appId: "anOtherTestApp",
|
appId: "anOtherTestApp",
|
||||||
masterKey: "anOtherTestMasterKey",
|
masterKey: "anOtherTestMasterKey",
|
||||||
serverURL: "http://localhost:12667/parse",
|
serverURL: "http://localhost:12667/parse",
|
||||||
databaseURI: 'mongodb://localhost:27017/anotherTstApp'
|
__indexBuildCompletionCallbackForTests: promise => {
|
||||||
});
|
promise
|
||||||
|
.then(() => {
|
||||||
|
expect(Parse.applicationId).toEqual("anOtherTestApp");
|
||||||
|
let app = express();
|
||||||
|
app.use('/parse', parseServer);
|
||||||
|
|
||||||
expect(Parse.applicationId).toEqual("anOtherTestApp");
|
server = app.listen(12667);
|
||||||
var app = express();
|
let obj = new Parse.Object("AnObject");
|
||||||
app.use('/parse', parseServer);
|
return obj.save()
|
||||||
|
})
|
||||||
var server = app.listen(12667);
|
.then(obj => {
|
||||||
var obj = new Parse.Object("AnObject");
|
objId = obj.id;
|
||||||
var objId;
|
let q = new Parse.Query("AnObject");
|
||||||
obj.save().then((obj) => {
|
return q.first();
|
||||||
objId = obj.id;
|
})
|
||||||
var q = new Parse.Query("AnObject");
|
.then(obj => {
|
||||||
return q.first();
|
expect(obj.id).toEqual(objId);
|
||||||
}).then((obj) => {
|
server.close(done);
|
||||||
expect(obj.id).toEqual(objId);
|
})
|
||||||
server.close();
|
.catch(error => {
|
||||||
done();
|
fail(JSON.stringify(error))
|
||||||
}).fail((err) => {
|
done();
|
||||||
server.close();
|
});
|
||||||
done();
|
}}
|
||||||
})
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has createLiveQueryServer', done => {
|
it('has createLiveQueryServer', done => {
|
||||||
@@ -273,96 +243,65 @@ describe('server', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('properly gives publicServerURL when set', done => {
|
it('properly gives publicServerURL when set', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({ publicServerURL: 'https://myserver.com/1' })
|
||||||
serverURL: 'http://localhost:8378/1',
|
.then(() => {
|
||||||
appId: 'test',
|
var config = new Config('test', 'http://localhost:8378/1');
|
||||||
masterKey: 'test',
|
expect(config.mount).toEqual('https://myserver.com/1');
|
||||||
publicServerURL: 'https://myserver.com/1'
|
done();
|
||||||
});
|
});
|
||||||
var config = new Config('test', 'http://localhost:8378/1');
|
|
||||||
expect(config.mount).toEqual('https://myserver.com/1');
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('properly removes trailing slash in mount', done => {
|
it('properly removes trailing slash in mount', done => {
|
||||||
setServerConfiguration({
|
reconfigureServer({})
|
||||||
serverURL: 'http://localhost:8378/1',
|
.then(() => {
|
||||||
appId: 'test',
|
var config = new Config('test', 'http://localhost:8378/1/');
|
||||||
masterKey: 'test'
|
expect(config.mount).toEqual('http://localhost:8378/1');
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
var config = new Config('test', 'http://localhost:8378/1/');
|
|
||||||
expect(config.mount).toEqual('http://localhost:8378/1');
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when getting invalid mount', done => {
|
it('should throw when getting invalid mount', done => {
|
||||||
expect(() => setServerConfiguration({
|
reconfigureServer({ publicServerURL: 'blabla:/some' })
|
||||||
serverURL: 'http://localhost:8378/1',
|
.catch(error => {
|
||||||
appId: 'test',
|
expect(error).toEqual('publicServerURL should be a valid HTTPS URL starting with https://')
|
||||||
masterKey: 'test',
|
done();
|
||||||
publicServerURL: 'blabla:/some'
|
})
|
||||||
}) ).toThrow("publicServerURL should be a valid HTTPS URL starting with https://");
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails if the session length is not a number', (done) => {
|
it('fails if the session length is not a number', done => {
|
||||||
expect(() => setServerConfiguration({
|
reconfigureServer({ sessionLength: 'test' })
|
||||||
serverURL: 'http://localhost:8378/1',
|
.catch(error => {
|
||||||
appId: 'test',
|
expect(error).toEqual('Session length must be a valid number.');
|
||||||
appName: 'unused',
|
done();
|
||||||
javascriptKey: 'test',
|
});
|
||||||
masterKey: 'test',
|
|
||||||
sessionLength: 'test'
|
|
||||||
})).toThrow('Session length must be a valid number.');
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails if the session length is less than or equal to 0', (done) => {
|
it('fails if the session length is less than or equal to 0', done => {
|
||||||
expect(() => setServerConfiguration({
|
reconfigureServer({ sessionLength: '-33' })
|
||||||
serverURL: 'http://localhost:8378/1',
|
.catch(error => {
|
||||||
appId: 'test',
|
expect(error).toEqual('Session length must be a value greater than 0.');
|
||||||
appName: 'unused',
|
return reconfigureServer({ sessionLength: '0' })
|
||||||
javascriptKey: 'test',
|
})
|
||||||
masterKey: 'test',
|
.catch(error => {
|
||||||
sessionLength: '-33'
|
expect(error).toEqual('Session length must be a value greater than 0.');
|
||||||
})).toThrow('Session length must be a value greater than 0.');
|
done();
|
||||||
|
});
|
||||||
expect(() => setServerConfiguration({
|
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
|
||||||
javascriptKey: 'test',
|
|
||||||
masterKey: 'test',
|
|
||||||
sessionLength: '0'
|
|
||||||
})).toThrow('Session length must be a value greater than 0.');
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores the session length when expireInactiveSessions set to false', (done) => {
|
it('ignores the session length when expireInactiveSessions set to false', (done) => {
|
||||||
expect(() => setServerConfiguration({
|
reconfigureServer({
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
|
||||||
javascriptKey: 'test',
|
|
||||||
masterKey: 'test',
|
|
||||||
sessionLength: '-33',
|
sessionLength: '-33',
|
||||||
expireInactiveSessions: false
|
expireInactiveSessions: false
|
||||||
})).not.toThrow();
|
})
|
||||||
|
.then(() => reconfigureServer({
|
||||||
expect(() => setServerConfiguration({
|
|
||||||
serverURL: 'http://localhost:8378/1',
|
|
||||||
appId: 'test',
|
|
||||||
appName: 'unused',
|
|
||||||
javascriptKey: 'test',
|
|
||||||
masterKey: 'test',
|
|
||||||
sessionLength: '0',
|
sessionLength: '0',
|
||||||
expireInactiveSessions: false
|
expireInactiveSessions: false
|
||||||
})).not.toThrow();
|
}))
|
||||||
done();
|
.then(done);
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fails if you try to set revokeSessionOnPasswordReset to non-boolean', done => {
|
it('fails if you try to set revokeSessionOnPasswordReset to non-boolean', done => {
|
||||||
expect(() => setServerConfiguration({ revokeSessionOnPasswordReset: 'non-bool' })).toThrow();
|
reconfigureServer({ revokeSessionOnPasswordReset: 'non-bool' })
|
||||||
done();
|
.catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export function loadAdapter(adapter, defaultAdapter, options) {
|
export function loadAdapter(adapter, defaultAdapter, options) {
|
||||||
if (!adapter)
|
if (!adapter) {
|
||||||
{
|
|
||||||
if (!defaultAdapter) {
|
if (!defaultAdapter) {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,18 @@ export default class MongoCollection {
|
|||||||
return this._mongoCollection.deleteMany(query);
|
return this._mongoCollection.deleteMany(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ensureSparseUniqueIndexInBackground(indexRequest) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this._mongoCollection.ensureIndex(indexRequest, { unique: true, background: true, sparse: true }, (error, indexName) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
drop() {
|
drop() {
|
||||||
return this._mongoCollection.drop();
|
return this._mongoCollection.drop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class MongoSchemaCollection {
|
|||||||
if (results.length === 1) {
|
if (results.length === 1) {
|
||||||
return mongoSchemaToParseSchema(results[0]);
|
return mongoSchemaToParseSchema(results[0]);
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject();
|
throw undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -175,9 +175,9 @@ class MongoSchemaCollection {
|
|||||||
.then(result => mongoSchemaToParseSchema(result.ops[0]))
|
.then(result => mongoSchemaToParseSchema(result.ops[0]))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.code === 11000) { //Mongo's duplicate key error
|
if (error.code === 11000) { //Mongo's duplicate key error
|
||||||
return Promise.reject();
|
throw undefined;
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,17 +207,17 @@ class MongoSchemaCollection {
|
|||||||
if (type.type === 'GeoPoint') {
|
if (type.type === 'GeoPoint') {
|
||||||
// Make sure there are not other geopoint fields
|
// Make sure there are not other geopoint fields
|
||||||
if (Object.keys(schema.fields).some(existingField => schema.fields[existingField].type === 'GeoPoint')) {
|
if (Object.keys(schema.fields).some(existingField => schema.fields[existingField].type === 'GeoPoint')) {
|
||||||
return Promise.reject(new Parse.Error(Parse.Error.INCORRECT_TYPE, 'MongoDB only supports one GeoPoint field in a class.'));
|
throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'MongoDB only supports one GeoPoint field in a class.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return;
|
||||||
}, error => {
|
}, error => {
|
||||||
// If error is undefined, the schema doesn't exist, and we can create the schema with the field.
|
// If error is undefined, the schema doesn't exist, and we can create the schema with the field.
|
||||||
// If some other error, reject with it.
|
// If some other error, reject with it.
|
||||||
if (error === undefined) {
|
if (error === undefined) {
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
throw Promise.reject(error);
|
throw error;
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// We use $exists and $set to avoid overwriting the field type if it
|
// We use $exists and $set to avoid overwriting the field type if it
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export class MongoStorageAdapter {
|
|||||||
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions).then(database => {
|
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions).then(database => {
|
||||||
this.database = database;
|
this.database = database;
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.connectionPromise;
|
return this.connectionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,9 +103,9 @@ export class MongoStorageAdapter {
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
// 'ns not found' means collection was already gone. Ignore deletion attempt.
|
// 'ns not found' means collection was already gone. Ignore deletion attempt.
|
||||||
if (error.message == 'ns not found') {
|
if (error.message == 'ns not found') {
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ export class MongoStorageAdapter {
|
|||||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE,
|
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE,
|
||||||
'A duplicate value for a field with unique values was provided');
|
'A duplicate value for a field with unique values was provided');
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,6 +237,28 @@ export class MongoStorageAdapter {
|
|||||||
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)));
|
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
|
||||||
|
// currently know which fields are nullable and which aren't, we ignore that criteria.
|
||||||
|
// As such, we shouldn't expose this function to users of parse until we have an out-of-band
|
||||||
|
// Way of determining if a field is nullable. Undefined doesn't count against uniqueness,
|
||||||
|
// which is why we use sparse indexes.
|
||||||
|
ensureUniqueness(className, fieldNames, schema) {
|
||||||
|
let indexCreationRequest = {};
|
||||||
|
let mongoFieldNames = fieldNames.map(fieldName => transformKey(className, fieldName, schema));
|
||||||
|
mongoFieldNames.forEach(fieldName => {
|
||||||
|
indexCreationRequest[fieldName] = 1;
|
||||||
|
});
|
||||||
|
return this.adaptiveCollection(className)
|
||||||
|
.then(collection => collection._ensureSparseUniqueIndexInBackground(indexCreationRequest))
|
||||||
|
.catch(error => {
|
||||||
|
if (error.code === 11000) {
|
||||||
|
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Tried to ensure field uniqueness for a class that already has duplicates.');
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Used in tests
|
// Used in tests
|
||||||
_rawFind(className, query) {
|
_rawFind(className, query) {
|
||||||
return this.adaptiveCollection(className).then(collection => collection.find(query));
|
return this.adaptiveCollection(className).then(collection => collection.find(query));
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ function removeTrailingSlash(str) {
|
|||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
constructor(applicationId: string, mount: string) {
|
constructor(applicationId: string, mount: string) {
|
||||||
let DatabaseAdapter = require('./DatabaseAdapter');
|
|
||||||
let cacheInfo = AppCache.get(applicationId);
|
let cacheInfo = AppCache.get(applicationId);
|
||||||
if (!cacheInfo) {
|
if (!cacheInfo) {
|
||||||
return;
|
return;
|
||||||
@@ -32,7 +31,7 @@ export class Config {
|
|||||||
this.fileKey = cacheInfo.fileKey;
|
this.fileKey = cacheInfo.fileKey;
|
||||||
this.facebookAppIds = cacheInfo.facebookAppIds;
|
this.facebookAppIds = cacheInfo.facebookAppIds;
|
||||||
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
|
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
|
||||||
this.database = DatabaseAdapter.getDatabaseConnection(applicationId, cacheInfo.collectionPrefix);
|
this.database = cacheInfo.databaseController;
|
||||||
|
|
||||||
this.serverURL = cacheInfo.serverURL;
|
this.serverURL = cacheInfo.serverURL;
|
||||||
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);
|
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);
|
||||||
@@ -55,24 +54,31 @@ export class Config {
|
|||||||
this.revokeSessionOnPasswordReset = cacheInfo.revokeSessionOnPasswordReset;
|
this.revokeSessionOnPasswordReset = cacheInfo.revokeSessionOnPasswordReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static validate(options) {
|
static validate({
|
||||||
|
verifyUserEmails,
|
||||||
|
appName,
|
||||||
|
publicServerURL,
|
||||||
|
revokeSessionOnPasswordReset,
|
||||||
|
expireInactiveSessions,
|
||||||
|
sessionLength,
|
||||||
|
}) {
|
||||||
this.validateEmailConfiguration({
|
this.validateEmailConfiguration({
|
||||||
verifyUserEmails: options.verifyUserEmails,
|
verifyUserEmails: verifyUserEmails,
|
||||||
appName: options.appName,
|
appName: appName,
|
||||||
publicServerURL: options.publicServerURL
|
publicServerURL: publicServerURL
|
||||||
})
|
})
|
||||||
|
|
||||||
if (typeof options.revokeSessionOnPasswordReset !== 'boolean') {
|
if (typeof revokeSessionOnPasswordReset !== 'boolean') {
|
||||||
throw 'revokeSessionOnPasswordReset must be a boolean value';
|
throw 'revokeSessionOnPasswordReset must be a boolean value';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.publicServerURL) {
|
if (publicServerURL) {
|
||||||
if (!options.publicServerURL.startsWith("http://") && !options.publicServerURL.startsWith("https://")) {
|
if (!publicServerURL.startsWith("http://") && !publicServerURL.startsWith("https://")) {
|
||||||
throw "publicServerURL should be a valid HTTPS URL starting with https://"
|
throw "publicServerURL should be a valid HTTPS URL starting with https://"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.validateSessionConfiguration(options.sessionLength, options.expireInactiveSessions);
|
this.validateSessionConfiguration(sessionLength, expireInactiveSessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
static validateEmailConfiguration({verifyUserEmails, appName, publicServerURL}) {
|
static validateEmailConfiguration({verifyUserEmails, appName, publicServerURL}) {
|
||||||
|
|||||||
@@ -61,19 +61,12 @@ function DatabaseController(adapter, { skipValidation } = {}) {
|
|||||||
// it. Instead, use loadSchema to get a schema.
|
// it. Instead, use loadSchema to get a schema.
|
||||||
this.schemaPromise = null;
|
this.schemaPromise = null;
|
||||||
this.skipValidation = !!skipValidation;
|
this.skipValidation = !!skipValidation;
|
||||||
this.connect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseController.prototype.WithoutValidation = function() {
|
DatabaseController.prototype.WithoutValidation = function() {
|
||||||
return new DatabaseController(this.adapter, {collectionPrefix: this.collectionPrefix, skipValidation: true});
|
return new DatabaseController(this.adapter, {collectionPrefix: this.collectionPrefix, skipValidation: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connects to the database. Returns a promise that resolves when the
|
|
||||||
// connection is successful.
|
|
||||||
DatabaseController.prototype.connect = function() {
|
|
||||||
return this.adapter.connect();
|
|
||||||
};
|
|
||||||
|
|
||||||
DatabaseController.prototype.schemaCollection = function() {
|
DatabaseController.prototype.schemaCollection = function() {
|
||||||
return this.adapter.schemaCollection();
|
return this.adapter.schemaCollection();
|
||||||
};
|
};
|
||||||
@@ -87,8 +80,7 @@ DatabaseController.prototype.validateClassName = function(className) {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
if (!SchemaController.classNameIsValid(className)) {
|
if (!SchemaController.classNameIsValid(className)) {
|
||||||
const error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className);
|
return Promise.reject(new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className));
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
@@ -417,7 +409,6 @@ DatabaseController.prototype.canAddField = function(schema, className, object, a
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deletes everything in the database matching the current collectionPrefix
|
|
||||||
// Won't delete collections in the system namespace
|
// Won't delete collections in the system namespace
|
||||||
// Returns a promise.
|
// Returns a promise.
|
||||||
DatabaseController.prototype.deleteEverything = function() {
|
DatabaseController.prototype.deleteEverything = function() {
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/** @flow weak */
|
/** @flow weak */
|
||||||
|
|
||||||
import * as DatabaseAdapter from "../DatabaseAdapter";
|
import * as DatabaseAdapter from "../DatabaseAdapter";
|
||||||
import * as triggers from "../triggers";
|
import * as triggers from "../triggers";
|
||||||
import * as Parse from "parse/node";
|
import * as Parse from "parse/node";
|
||||||
import * as request from "request";
|
import * as request from "request";
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
|
|
||||||
const DefaultHooksCollectionName = "_Hooks";
|
const DefaultHooksCollectionName = "_Hooks";
|
||||||
|
|
||||||
export class HooksController {
|
export class HooksController {
|
||||||
_applicationId:string;
|
_applicationId:string;
|
||||||
_collectionPrefix:string;
|
|
||||||
_collection;
|
|
||||||
|
|
||||||
constructor(applicationId:string, collectionPrefix:string = '', webhookKey) {
|
constructor(applicationId:string, databaseController, webhookKey) {
|
||||||
this._applicationId = applicationId;
|
this._applicationId = applicationId;
|
||||||
this._collectionPrefix = collectionPrefix;
|
|
||||||
this._webhookKey = webhookKey;
|
this._webhookKey = webhookKey;
|
||||||
this.database = DatabaseAdapter.getDatabaseConnection(this._applicationId, this._collectionPrefix).WithoutValidation();
|
this.database = databaseController;
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class UserController extends AdaptableController {
|
|||||||
if (!this.shouldVerifyEmails) {
|
if (!this.shouldVerifyEmails) {
|
||||||
// Trying to verify email when not enabled
|
// Trying to verify email when not enabled
|
||||||
// TODO: Better error here.
|
// TODO: Better error here.
|
||||||
return Promise.reject();
|
throw undefined;
|
||||||
}
|
}
|
||||||
let database = this.config.database.WithoutValidation();
|
let database = this.config.database.WithoutValidation();
|
||||||
return database.update('_User', {
|
return database.update('_User', {
|
||||||
@@ -51,7 +51,7 @@ export class UserController extends AdaptableController {
|
|||||||
_email_verify_token: token
|
_email_verify_token: token
|
||||||
}, {emailVerified: true}).then(document => {
|
}, {emailVerified: true}).then(document => {
|
||||||
if (!document) {
|
if (!document) {
|
||||||
return Promise.reject();
|
throw undefined;
|
||||||
}
|
}
|
||||||
return Promise.resolve(document);
|
return Promise.resolve(document);
|
||||||
});
|
});
|
||||||
@@ -64,7 +64,7 @@ export class UserController extends AdaptableController {
|
|||||||
_perishable_token: token
|
_perishable_token: token
|
||||||
}, {limit: 1}).then(results => {
|
}, {limit: 1}).then(results => {
|
||||||
if (results.length != 1) {
|
if (results.length != 1) {
|
||||||
return Promise.reject();
|
throw undefined;
|
||||||
}
|
}
|
||||||
return results[0];
|
return results[0];
|
||||||
});
|
});
|
||||||
@@ -85,7 +85,7 @@ export class UserController extends AdaptableController {
|
|||||||
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
|
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
|
||||||
return query.execute().then(function(result){
|
return query.execute().then(function(result){
|
||||||
if (result.results.length != 1) {
|
if (result.results.length != 1) {
|
||||||
return Promise.reject();
|
throw undefined;
|
||||||
}
|
}
|
||||||
return result.results[0];
|
return result.results[0];
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,74 +1,21 @@
|
|||||||
/** @flow weak */
|
import AppCache from './cache';
|
||||||
// Database Adapter
|
|
||||||
//
|
|
||||||
// Allows you to change the underlying database.
|
|
||||||
//
|
|
||||||
// Adapter classes must implement the following methods:
|
|
||||||
// * a constructor with signature (connectionString, optionsObject)
|
|
||||||
// * connect()
|
|
||||||
// * loadSchema()
|
|
||||||
// * create(className, object)
|
|
||||||
// * find(className, query, options)
|
|
||||||
// * update(className, query, update, options)
|
|
||||||
// * destroy(className, query, options)
|
|
||||||
// * This list is incomplete and the database process is not fully modularized.
|
|
||||||
//
|
|
||||||
// Default is MongoStorageAdapter.
|
|
||||||
|
|
||||||
import DatabaseController from './Controllers/DatabaseController';
|
|
||||||
import MongoStorageAdapter from './Adapters/Storage/Mongo/MongoStorageAdapter';
|
|
||||||
|
|
||||||
let dbConnections = {};
|
|
||||||
let appDatabaseURIs = {};
|
|
||||||
let appDatabaseOptions = {};
|
|
||||||
|
|
||||||
function setAppDatabaseURI(appId, uri) {
|
|
||||||
appDatabaseURIs[appId] = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAppDatabaseOptions(appId: string, options: Object) {
|
|
||||||
appDatabaseOptions[appId] = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Used by tests
|
|
||||||
function clearDatabaseSettings() {
|
|
||||||
appDatabaseURIs = {};
|
|
||||||
dbConnections = {};
|
|
||||||
appDatabaseOptions = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
//Used by tests
|
//Used by tests
|
||||||
function destroyAllDataPermanently() {
|
function destroyAllDataPermanently() {
|
||||||
if (process.env.TESTING) {
|
if (process.env.TESTING) {
|
||||||
var promises = [];
|
// This is super janky, but destroyAllDataPermanently is
|
||||||
for (var conn in dbConnections) {
|
// a janky interface, so we need to have some jankyness
|
||||||
promises.push(dbConnections[conn].deleteEverything());
|
// to support it
|
||||||
}
|
return Promise.all(Object.keys(AppCache.cache).map(appId => {
|
||||||
return Promise.all(promises);
|
const app = AppCache.get(appId);
|
||||||
|
if (app.databaseController) {
|
||||||
|
return app.databaseController.deleteEverything();
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
throw 'Only supported in test environment';
|
throw 'Only supported in test environment';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDatabaseConnection(appId: string, collectionPrefix: string) {
|
module.exports = { destroyAllDataPermanently };
|
||||||
if (dbConnections[appId]) {
|
|
||||||
return dbConnections[appId];
|
|
||||||
}
|
|
||||||
|
|
||||||
let mongoAdapterOptions = {
|
|
||||||
collectionPrefix: collectionPrefix,
|
|
||||||
mongoOptions: appDatabaseOptions[appId],
|
|
||||||
uri: appDatabaseURIs[appId], //may be undefined if the user didn't supply a URI, in which case the default will be used
|
|
||||||
}
|
|
||||||
|
|
||||||
dbConnections[appId] = new DatabaseController(new MongoStorageAdapter(mongoAdapterOptions), {appId: appId});
|
|
||||||
|
|
||||||
return dbConnections[appId];
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getDatabaseConnection: getDatabaseConnection,
|
|
||||||
setAppDatabaseOptions: setAppDatabaseOptions,
|
|
||||||
setAppDatabaseURI: setAppDatabaseURI,
|
|
||||||
clearDatabaseSettings: clearDatabaseSettings,
|
|
||||||
destroyAllDataPermanently: destroyAllDataPermanently,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -51,10 +51,17 @@ import { SessionsRouter } from './Routers/SessionsRouter';
|
|||||||
import { UserController } from './Controllers/UserController';
|
import { UserController } from './Controllers/UserController';
|
||||||
import { UsersRouter } from './Routers/UsersRouter';
|
import { UsersRouter } from './Routers/UsersRouter';
|
||||||
|
|
||||||
|
import DatabaseController from './Controllers/DatabaseController';
|
||||||
|
const SchemaController = require('./Controllers/SchemaController');
|
||||||
import ParsePushAdapter from 'parse-server-push-adapter';
|
import ParsePushAdapter from 'parse-server-push-adapter';
|
||||||
|
import MongoStorageAdapter from './Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||||
// Mutate the Parse object to add the Cloud Code handlers
|
// Mutate the Parse object to add the Cloud Code handlers
|
||||||
addParseCloud();
|
addParseCloud();
|
||||||
|
|
||||||
|
|
||||||
|
const requiredUserFields = { fields: { ...SchemaController.defaultColumns._Default, ...SchemaController.defaultColumns._User } };
|
||||||
|
|
||||||
|
|
||||||
// ParseServer works like a constructor of an express app.
|
// ParseServer works like a constructor of an express app.
|
||||||
// The args that we understand are:
|
// The args that we understand are:
|
||||||
// "filesAdapter": a class like GridStoreAdapter providing create, get,
|
// "filesAdapter": a class like GridStoreAdapter providing create, get,
|
||||||
@@ -88,6 +95,7 @@ class ParseServer {
|
|||||||
masterKey = requiredParameter('You must provide a masterKey!'),
|
masterKey = requiredParameter('You must provide a masterKey!'),
|
||||||
appName,
|
appName,
|
||||||
filesAdapter,
|
filesAdapter,
|
||||||
|
databaseAdapter,
|
||||||
push,
|
push,
|
||||||
loggerAdapter,
|
loggerAdapter,
|
||||||
logsFolder,
|
logsFolder,
|
||||||
@@ -122,23 +130,34 @@ class ParseServer {
|
|||||||
expireInactiveSessions = true,
|
expireInactiveSessions = true,
|
||||||
verbose = false,
|
verbose = false,
|
||||||
revokeSessionOnPasswordReset = true,
|
revokeSessionOnPasswordReset = true,
|
||||||
|
__indexBuildCompletionCallbackForTests = () => {},
|
||||||
}) {
|
}) {
|
||||||
// Initialize the node client SDK automatically
|
// Initialize the node client SDK automatically
|
||||||
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
||||||
Parse.serverURL = serverURL;
|
Parse.serverURL = serverURL;
|
||||||
|
|
||||||
|
if ((databaseOptions || databaseURI || collectionPrefix !== '') && databaseAdapter) {
|
||||||
|
throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/connectionPrefix.';
|
||||||
|
} else if (!databaseAdapter) {
|
||||||
|
databaseAdapter = new MongoStorageAdapter({
|
||||||
|
uri: databaseURI,
|
||||||
|
collectionPrefix,
|
||||||
|
mongoOptions: databaseOptions,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
databaseAdapter = loadAdapter(databaseAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filesAdapter && !databaseURI) {
|
||||||
|
throw 'When using an explicit database adapter, you must also use and explicit filesAdapter.';
|
||||||
|
}
|
||||||
|
|
||||||
if (logsFolder) {
|
if (logsFolder) {
|
||||||
configureLogger({
|
configureLogger({
|
||||||
logsFolder
|
logsFolder
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (databaseOptions) {
|
|
||||||
DatabaseAdapter.setAppDatabaseOptions(appId, databaseOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
DatabaseAdapter.setAppDatabaseURI(appId, databaseURI);
|
|
||||||
|
|
||||||
if (cloud) {
|
if (cloud) {
|
||||||
addParseCloud();
|
addParseCloud();
|
||||||
if (typeof cloud === 'function') {
|
if (typeof cloud === 'function') {
|
||||||
@@ -168,10 +187,23 @@ class ParseServer {
|
|||||||
const filesController = new FilesController(filesControllerAdapter, appId);
|
const filesController = new FilesController(filesControllerAdapter, appId);
|
||||||
const pushController = new PushController(pushControllerAdapter, appId);
|
const pushController = new PushController(pushControllerAdapter, appId);
|
||||||
const loggerController = new LoggerController(loggerControllerAdapter, appId);
|
const loggerController = new LoggerController(loggerControllerAdapter, appId);
|
||||||
const hooksController = new HooksController(appId, collectionPrefix, webhookKey);
|
|
||||||
const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails });
|
const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails });
|
||||||
const liveQueryController = new LiveQueryController(liveQuery);
|
const liveQueryController = new LiveQueryController(liveQuery);
|
||||||
const cacheController = new CacheController(cacheControllerAdapter, appId);
|
const cacheController = new CacheController(cacheControllerAdapter, appId);
|
||||||
|
const databaseController = new DatabaseController(databaseAdapter);
|
||||||
|
const hooksController = new HooksController(appId, databaseController, webhookKey);
|
||||||
|
|
||||||
|
let usernameUniqueness = databaseController.adapter.ensureUniqueness('_User', ['username'], requiredUserFields)
|
||||||
|
.catch(error => {
|
||||||
|
logger.warn('Unable to ensure uniqueness for usernames: ', error);
|
||||||
|
return Promise.reject();
|
||||||
|
});
|
||||||
|
|
||||||
|
let emailUniqueness = databaseController.adapter.ensureUniqueness('_User', ['email'], requiredUserFields)
|
||||||
|
.catch(error => {
|
||||||
|
logger.warn('Unabled to ensure uniqueness for user email addresses: ', error);
|
||||||
|
return Promise.reject();
|
||||||
|
})
|
||||||
|
|
||||||
AppCache.put(appId, {
|
AppCache.put(appId, {
|
||||||
masterKey: masterKey,
|
masterKey: masterKey,
|
||||||
@@ -200,7 +232,8 @@ class ParseServer {
|
|||||||
liveQueryController: liveQueryController,
|
liveQueryController: liveQueryController,
|
||||||
sessionLength: Number(sessionLength),
|
sessionLength: Number(sessionLength),
|
||||||
expireInactiveSessions: expireInactiveSessions,
|
expireInactiveSessions: expireInactiveSessions,
|
||||||
revokeSessionOnPasswordReset
|
revokeSessionOnPasswordReset,
|
||||||
|
databaseController,
|
||||||
});
|
});
|
||||||
|
|
||||||
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
||||||
@@ -211,6 +244,11 @@ class ParseServer {
|
|||||||
Config.validate(AppCache.get(appId));
|
Config.validate(AppCache.get(appId));
|
||||||
this.config = AppCache.get(appId);
|
this.config = AppCache.get(appId);
|
||||||
hooksController.load();
|
hooksController.load();
|
||||||
|
|
||||||
|
// Note: Tests will start to fail if any validation happens after this is called.
|
||||||
|
if (process.env.TESTING) {
|
||||||
|
__indexBuildCompletionCallbackForTests(Promise.all([usernameUniqueness, emailUniqueness]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get app() {
|
get app() {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
// components that external developers may be modifying.
|
// components that external developers may be modifying.
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
import log from './logger';
|
import log from './logger';
|
||||||
|
|
||||||
export default class PromiseRouter {
|
export default class PromiseRouter {
|
||||||
// Each entry should be an object with:
|
// Each entry should be an object with:
|
||||||
|
|||||||
100
src/RestWrite.js
100
src/RestWrite.js
@@ -105,9 +105,9 @@ RestWrite.prototype.getUserAndRoleACL = function() {
|
|||||||
return this.auth.getUserRoles().then((roles) => {
|
return this.auth.getUserRoles().then((roles) => {
|
||||||
roles.push(this.auth.user.id);
|
roles.push(this.auth.user.id);
|
||||||
this.runOptions.acl = this.runOptions.acl.concat(roles);
|
this.runOptions.acl = this.runOptions.acl.concat(roles);
|
||||||
return Promise.resolve();
|
return;
|
||||||
});
|
});
|
||||||
}else{
|
} else {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -119,7 +119,7 @@ RestWrite.prototype.validateClientClassCreation = function() {
|
|||||||
&& sysClass.indexOf(this.className) === -1) {
|
&& sysClass.indexOf(this.className) === -1) {
|
||||||
return this.config.database.collectionExists(this.className).then((hasClass) => {
|
return this.config.database.collectionExists(this.className).then((hasClass) => {
|
||||||
if (hasClass === true) {
|
if (hasClass === true) {
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
|
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
|
||||||
@@ -309,7 +309,7 @@ RestWrite.prototype.handleAuthData = function(authData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,45 +356,43 @@ RestWrite.prototype.transformUser = function() {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// We need to a find to check for duplicate username in case they are missing the unique index on usernames
|
||||||
|
// TODO: Check if there is a unique index, and if so, skip this query.
|
||||||
return this.config.database.find(
|
return this.config.database.find(
|
||||||
this.className, {
|
this.className,
|
||||||
username: this.data.username,
|
{ username: this.data.username, objectId: {'$ne': this.objectId()} },
|
||||||
objectId: {'$ne': this.objectId()}
|
{ limit: 1 }
|
||||||
}, {limit: 1}).then((results) => {
|
)
|
||||||
if (results.length > 0) {
|
.then(results => {
|
||||||
throw new Parse.Error(Parse.Error.USERNAME_TAKEN,
|
if (results.length > 0) {
|
||||||
'Account already exists for this username');
|
throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.');
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return;
|
||||||
});
|
});
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
if (!this.data.email || this.data.email.__op === 'Delete') {
|
if (!this.data.email || this.data.email.__op === 'Delete') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Validate basic email address format
|
// Validate basic email address format
|
||||||
if (!this.data.email.match(/^.+@.+$/)) {
|
if (!this.data.email.match(/^.+@.+$/)) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_EMAIL_ADDRESS,
|
throw new Parse.Error(Parse.Error.INVALID_EMAIL_ADDRESS, 'Email address format is invalid.');
|
||||||
'Email address format is invalid.');
|
|
||||||
}
|
}
|
||||||
// Check for email uniqueness
|
// Same problem for email as above for username
|
||||||
return this.config.database.find(
|
return this.config.database.find(
|
||||||
this.className, {
|
this.className,
|
||||||
email: this.data.email,
|
{ email: this.data.email, objectId: {'$ne': this.objectId()} },
|
||||||
objectId: {'$ne': this.objectId()}
|
{ limit: 1 }
|
||||||
}, {limit: 1}).then((results) => {
|
)
|
||||||
if (results.length > 0) {
|
.then(results => {
|
||||||
throw new Parse.Error(Parse.Error.EMAIL_TAKEN,
|
if (results.length > 0) {
|
||||||
'Account already exists for this email ' +
|
throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.');
|
||||||
'address');
|
}
|
||||||
}
|
// We updated the email, send a new validation
|
||||||
return Promise.resolve();
|
this.storage['sendVerificationEmail'] = true;
|
||||||
}).then(() => {
|
this.config.userController.setEmailVerifyToken(this.data);
|
||||||
// We updated the email, send a new validation
|
});
|
||||||
this.storage['sendVerificationEmail'] = true;
|
})
|
||||||
this.config.userController.setEmailVerifyToken(this.data);
|
|
||||||
return Promise.resolve();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
RestWrite.prototype.createSessionTokenIfNeeded = function() {
|
RestWrite.prototype.createSessionTokenIfNeeded = function() {
|
||||||
@@ -577,7 +575,7 @@ RestWrite.prototype.handleInstallation = function() {
|
|||||||
'deviceType may not be changed in this ' +
|
'deviceType may not be changed in this ' +
|
||||||
'operation');
|
'operation');
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -762,6 +760,36 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
|||||||
|
|
||||||
// Run a create
|
// Run a create
|
||||||
return this.config.database.create(this.className, this.data, this.runOptions)
|
return this.config.database.create(this.className, this.data, this.runOptions)
|
||||||
|
.catch(error => {
|
||||||
|
if (this.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
// If this was a failed user creation due to username or email already taken, we need to
|
||||||
|
// check whether it was username or email and return the appropriate error.
|
||||||
|
|
||||||
|
// TODO: See if we can later do this without additional queries by using named indexes.
|
||||||
|
return this.config.database.find(
|
||||||
|
this.className,
|
||||||
|
{ username: this.data.username, objectId: {'$ne': this.objectId()} },
|
||||||
|
{ limit: 1 }
|
||||||
|
)
|
||||||
|
.then(results => {
|
||||||
|
if (results.length > 0) {
|
||||||
|
throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.');
|
||||||
|
}
|
||||||
|
return this.config.database.find(
|
||||||
|
this.className,
|
||||||
|
{ email: this.data.email, objectId: {'$ne': this.objectId()} },
|
||||||
|
{ limit: 1 }
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(results => {
|
||||||
|
if (results.length > 0) {
|
||||||
|
throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.');
|
||||||
|
}
|
||||||
|
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
|
||||||
|
});
|
||||||
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
response.objectId = this.data.objectId;
|
response.objectId = this.data.objectId;
|
||||||
response.createdAt = this.data.createdAt;
|
response.createdAt = this.data.createdAt;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import BodyParser from 'body-parser';
|
import BodyParser from 'body-parser';
|
||||||
import * as Middlewares from '../middlewares';
|
import * as Middlewares from '../middlewares';
|
||||||
import { randomHexString } from '../cryptoUtils';
|
import { randomHexString } from '../cryptoUtils';
|
||||||
import Config from '../Config';
|
import Config from '../Config';
|
||||||
import mime from 'mime';
|
import mime from 'mime';
|
||||||
|
|
||||||
export class FilesRouter {
|
export class FilesRouter {
|
||||||
|
|
||||||
@@ -77,8 +77,7 @@ export class FilesRouter {
|
|||||||
res.set('Location', result.url);
|
res.set('Location', result.url);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
|
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Could not store file.'));
|
||||||
'Could not store file.'));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,4 +92,4 @@ export class FilesRouter {
|
|||||||
'Could not delete file.'));
|
'Could not delete file.'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Parse } from 'parse/node';
|
import { Parse } from 'parse/node';
|
||||||
import PromiseRouter from '../PromiseRouter';
|
import PromiseRouter from '../PromiseRouter';
|
||||||
import { HooksController } from '../Controllers/HooksController';
|
|
||||||
import * as middleware from "../middlewares";
|
import * as middleware from "../middlewares";
|
||||||
|
|
||||||
export class HooksRouter extends PromiseRouter {
|
export class HooksRouter extends PromiseRouter {
|
||||||
@@ -26,7 +25,7 @@ export class HooksRouter extends PromiseRouter {
|
|||||||
return Promise.resolve({response: foundFunction});
|
return Promise.resolve({response: foundFunction});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return hooksController.getFunctions().then((functions) => {
|
return hooksController.getFunctions().then((functions) => {
|
||||||
return { response: functions || [] };
|
return { response: functions || [] };
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
@@ -37,7 +36,7 @@ export class HooksRouter extends PromiseRouter {
|
|||||||
handleGetTriggers(req) {
|
handleGetTriggers(req) {
|
||||||
var hooksController = req.config.hooksController;
|
var hooksController = req.config.hooksController;
|
||||||
if (req.params.className && req.params.triggerName) {
|
if (req.params.className && req.params.triggerName) {
|
||||||
|
|
||||||
return hooksController.getTrigger(req.params.className, req.params.triggerName).then((foundTrigger) => {
|
return hooksController.getTrigger(req.params.className, req.params.triggerName).then((foundTrigger) => {
|
||||||
if (!foundTrigger) {
|
if (!foundTrigger) {
|
||||||
throw new Parse.Error(143,`class ${req.params.className} does not exist`);
|
throw new Parse.Error(143,`class ${req.params.className} does not exist`);
|
||||||
@@ -45,7 +44,7 @@ export class HooksRouter extends PromiseRouter {
|
|||||||
return Promise.resolve({response: foundTrigger});
|
return Promise.resolve({response: foundTrigger});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return hooksController.getTriggers().then((triggers) => ({ response: triggers || [] }));
|
return hooksController.getTriggers().then((triggers) => ({ response: triggers || [] }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,10 +72,10 @@ export class HooksRouter extends PromiseRouter {
|
|||||||
hook.url = req.body.url
|
hook.url = req.body.url
|
||||||
} else {
|
} else {
|
||||||
throw new Parse.Error(143, "invalid hook declaration");
|
throw new Parse.Error(143, "invalid hook declaration");
|
||||||
}
|
}
|
||||||
return this.updateHook(hook, req.config);
|
return this.updateHook(hook, req.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePut(req) {
|
handlePut(req) {
|
||||||
var body = req.body;
|
var body = req.body;
|
||||||
if (body.__op == "Delete") {
|
if (body.__op == "Delete") {
|
||||||
@@ -85,7 +84,7 @@ export class HooksRouter extends PromiseRouter {
|
|||||||
return this.handleUpdate(req);
|
return this.handleUpdate(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mountRoutes() {
|
mountRoutes() {
|
||||||
this.route('GET', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this));
|
this.route('GET', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this));
|
||||||
this.route('GET', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this));
|
this.route('GET', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this));
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ function del(config, auth, className, objectId) {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (!auth.isMaster) {
|
if (!auth.isMaster) {
|
||||||
return auth.getUserRoles();
|
return auth.getUserRoles();
|
||||||
}else{
|
} else {
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
var options = {};
|
var options = {};
|
||||||
@@ -87,7 +87,7 @@ function del(config, auth, className, objectId) {
|
|||||||
}, options);
|
}, options);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
triggers.maybeRunTrigger(triggers.Types.afterDelete, auth, inflatedObject, null, config);
|
triggers.maybeRunTrigger(triggers.Types.afterDelete, auth, inflatedObject, null, config);
|
||||||
return Promise.resolve();
|
return;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ function createApp(req, res) {
|
|||||||
var appId = cryptoUtils.randomHexString(32);
|
var appId = cryptoUtils.randomHexString(32);
|
||||||
|
|
||||||
ParseServer({
|
ParseServer({
|
||||||
|
databaseURI: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase',
|
||||||
appId: appId,
|
appId: appId,
|
||||||
masterKey: 'master',
|
masterKey: 'master',
|
||||||
serverURL: Parse.serverURL,
|
serverURL: Parse.serverURL,
|
||||||
|
|||||||
Reference in New Issue
Block a user