Merged with master
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -30,4 +30,7 @@ node_modules
|
|||||||
*~
|
*~
|
||||||
|
|
||||||
# WebStorm/IntelliJ
|
# WebStorm/IntelliJ
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# Babel.js
|
||||||
|
lib/
|
||||||
|
|||||||
@@ -8,4 +8,7 @@ node_js:
|
|||||||
env:
|
env:
|
||||||
- MONGODB_VERSION=2.6.11
|
- MONGODB_VERSION=2.6.11
|
||||||
- MONGODB_VERSION=3.0.8
|
- MONGODB_VERSION=3.0.8
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.mongodb/versions/downloads
|
||||||
after_success: ./node_modules/.bin/codecov
|
after_success: ./node_modules/.bin/codecov
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ We really want Parse to be yours, to see it grow and thrive in the open source c
|
|||||||
##### Please Do's
|
##### Please Do's
|
||||||
|
|
||||||
* Take testing seriously! Aim to increase the test coverage with every pull request.
|
* Take testing seriously! Aim to increase the test coverage with every pull request.
|
||||||
* Run the tests for the file you are working on with `TESTING=1 (repo-root)/node_modules/jasmine/bin/jasmine.js spec/MyFile.spec.js`
|
* Run the tests for the file you are working on with `npm test spec/MyFile.spec.js`
|
||||||
* Run the tests for the whole project and look at the coverage report to make sure your tests are exhaustive by running `npm test` and looking at (project-root)/lcov-report/parse-server/FileUnderTest.js.html
|
* Run the tests for the whole project and look at the coverage report to make sure your tests are exhaustive by running `npm test` and looking at (project-root)/lcov-report/parse-server/FileUnderTest.js.html
|
||||||
|
|
||||||
##### Code of Conduct
|
##### Code of Conduct
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var ParseServer = require("../index").ParseServer;
|
var ParseServer = require("../lib/index").ParseServer;
|
||||||
|
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
|
|||||||
17
package.json
17
package.json
@@ -2,7 +2,7 @@
|
|||||||
"name": "parse-server",
|
"name": "parse-server",
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"description": "An express module providing a Parse-compatible API server",
|
"description": "An express module providing a Parse-compatible API server",
|
||||||
"main": "index.js",
|
"main": "lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/ParsePlatform/parse-server"
|
"url": "https://github.com/ParsePlatform/parse-server"
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apn": "^1.7.5",
|
"apn": "^1.7.5",
|
||||||
"aws-sdk": "~2.2.33",
|
"aws-sdk": "~2.2.33",
|
||||||
|
"babel-runtime": "^6.5.0",
|
||||||
"bcrypt-nodejs": "0.0.3",
|
"bcrypt-nodejs": "0.0.3",
|
||||||
"body-parser": "^1.14.2",
|
"body-parser": "^1.14.2",
|
||||||
"deepcopy": "^0.6.1",
|
"deepcopy": "^0.6.1",
|
||||||
@@ -19,23 +20,29 @@
|
|||||||
"mime": "^1.3.4",
|
"mime": "^1.3.4",
|
||||||
"mongodb": "~2.1.0",
|
"mongodb": "~2.1.0",
|
||||||
"multer": "^1.1.0",
|
"multer": "^1.1.0",
|
||||||
|
"node-gcm": "^0.14.0",
|
||||||
"parse": "^1.7.0",
|
"parse": "^1.7.0",
|
||||||
"randomstring": "^1.1.3",
|
"randomstring": "^1.1.3",
|
||||||
"node-gcm": "^0.14.0",
|
|
||||||
"request": "^2.65.0"
|
"request": "^2.65.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.5.1",
|
||||||
|
"babel-core": "^6.5.1",
|
||||||
|
"babel-istanbul": "^0.6.0",
|
||||||
|
"babel-preset-es2015": "^6.5.0",
|
||||||
|
"babel-register": "^6.5.1",
|
||||||
"codecov": "^1.0.1",
|
"codecov": "^1.0.1",
|
||||||
"deep-diff": "^0.3.3",
|
"deep-diff": "^0.3.3",
|
||||||
"istanbul": "^0.4.2",
|
|
||||||
"jasmine": "^2.3.2",
|
"jasmine": "^2.3.2",
|
||||||
"mongodb-runner": "^3.1.15"
|
"mongodb-runner": "^3.1.15"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build": "./node_modules/.bin/babel src/ -d lib/",
|
||||||
"pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start",
|
"pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start",
|
||||||
"test": "NODE_ENV=test TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine",
|
"test": "NODE_ENV=test TESTING=1 ./node_modules/.bin/babel-node ./node_modules/.bin/babel-istanbul cover -x **/spec/** ./node_modules/.bin/jasmine",
|
||||||
"posttest": "mongodb-runner stop",
|
"posttest": "mongodb-runner stop",
|
||||||
"start": "./bin/parse-server"
|
"start": "./bin/parse-server",
|
||||||
|
"prepublish": "npm run build"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.1"
|
"node": ">=4.1"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var APNS = require('../APNS');
|
var APNS = require('../src/APNS');
|
||||||
|
|
||||||
describe('APNS', () => {
|
describe('APNS', () => {
|
||||||
it('can generate APNS notification', (done) => {
|
it('can generate APNS notification', (done) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var ExportAdapter = require('../ExportAdapter');
|
var ExportAdapter = require('../src/ExportAdapter');
|
||||||
|
|
||||||
describe('ExportAdapter', () => {
|
describe('ExportAdapter', () => {
|
||||||
it('can be constructed', (done) => {
|
it('can be constructed', (done) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var GCM = require('../GCM');
|
var GCM = require('../src/GCM');
|
||||||
|
|
||||||
describe('GCM', () => {
|
describe('GCM', () => {
|
||||||
it('can generate GCM Payload without expiration time', (done) => {
|
it('can generate GCM Payload without expiration time', (done) => {
|
||||||
|
|||||||
@@ -251,6 +251,9 @@ describe('Parse.ACL', () => {
|
|||||||
equal(results.length, 1);
|
equal(results.length, 1);
|
||||||
var result = results[0];
|
var result = results[0];
|
||||||
ok(result);
|
ok(result);
|
||||||
|
if (!result) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
equal(result.id, object.id);
|
equal(result.id, object.id);
|
||||||
equal(result.getACL().getReadAccess(user), true);
|
equal(result.getACL().getReadAccess(user), true);
|
||||||
equal(result.getACL().getWriteAccess(user), true);
|
equal(result.getACL().getWriteAccess(user), true);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// A bunch of different tests are in here - it isn't very thematic.
|
// A bunch of different tests are in here - it isn't very thematic.
|
||||||
// It would probably be better to refactor them into different files.
|
// It would probably be better to refactor them into different files.
|
||||||
|
|
||||||
var DatabaseAdapter = require('../DatabaseAdapter');
|
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
|
|
||||||
describe('miscellaneous', function() {
|
describe('miscellaneous', function() {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// These tests check the Installations functionality of the REST API.
|
// These tests check the Installations functionality of the REST API.
|
||||||
// Ported from installation_collection_test.go
|
// Ported from installation_collection_test.go
|
||||||
|
|
||||||
var auth = require('../Auth');
|
var auth = require('../src/Auth');
|
||||||
var cache = require('../cache');
|
var cache = require('../src/cache');
|
||||||
var Config = require('../Config');
|
var Config = require('../src/Config');
|
||||||
var DatabaseAdapter = require('../DatabaseAdapter');
|
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
var rest = require('../rest');
|
var rest = require('../src/rest');
|
||||||
|
|
||||||
var config = new Config('test');
|
var config = new Config('test');
|
||||||
var database = DatabaseAdapter.getDatabaseConnection('test');
|
var database = DatabaseAdapter.getDatabaseConnection('test');
|
||||||
|
|||||||
@@ -2056,7 +2056,7 @@ describe('Parse.Query testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('query match on array value', (done) => {
|
it('query match on array with single object', (done) => {
|
||||||
var target = {__type: 'Pointer', className: 'TestObject', objectId: 'abc123'};
|
var target = {__type: 'Pointer', className: 'TestObject', objectId: 'abc123'};
|
||||||
var obj = new Parse.Object('TestObject');
|
var obj = new Parse.Object('TestObject');
|
||||||
obj.set('someObjs', [target]);
|
obj.set('someObjs', [target]);
|
||||||
@@ -2072,4 +2072,20 @@ describe('Parse.Query testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('query match on array with multiple objects', (done) => {
|
||||||
|
var target1 = {__type: 'Pointer', className: 'TestObject', objectId: 'abc'};
|
||||||
|
var target2 = {__type: 'Pointer', className: 'TestObject', objectId: '123'};
|
||||||
|
var obj= new Parse.Object('TestObject');
|
||||||
|
obj.set('someObjs', [target1, target2]);
|
||||||
|
obj.save().then(() => {
|
||||||
|
var query = new Parse.Query('TestObject');
|
||||||
|
query.equalTo('someObjs', target1);
|
||||||
|
return query.find();
|
||||||
|
}).then((results) => {
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
done();
|
||||||
|
}, (error) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Tests that involve sending password reset emails.
|
// Tests that involve sending password reset emails.
|
||||||
|
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var passwordCrypto = require('../password');
|
var passwordCrypto = require('../src/password');
|
||||||
|
|
||||||
describe('Parse.User testing', () => {
|
describe('Parse.User testing', () => {
|
||||||
it("user sign up class method", (done) => {
|
it("user sign up class method", (done) => {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// These tests check the "create" functionality of the REST API.
|
// These tests check the "create" functionality of the REST API.
|
||||||
var auth = require('../Auth');
|
var auth = require('../src/Auth');
|
||||||
var cache = require('../cache');
|
var cache = require('../src/cache');
|
||||||
var Config = require('../Config');
|
var Config = require('../src/Config');
|
||||||
var DatabaseAdapter = require('../DatabaseAdapter');
|
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
var rest = require('../rest');
|
var rest = require('../src/rest');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
|
|
||||||
var config = new Config('test');
|
var config = new Config('test');
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// These tests check the "find" functionality of the REST API.
|
// These tests check the "find" functionality of the REST API.
|
||||||
var auth = require('../Auth');
|
var auth = require('../src/Auth');
|
||||||
var cache = require('../cache');
|
var cache = require('../src/cache');
|
||||||
var Config = require('../Config');
|
var Config = require('../src/Config');
|
||||||
var rest = require('../rest');
|
var rest = require('../src/rest');
|
||||||
|
|
||||||
var config = new Config('test');
|
var config = new Config('test');
|
||||||
var nobody = auth.nobody(config);
|
var nobody = auth.nobody(config);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// These tests check that the Schema operates correctly.
|
// These tests check that the Schema operates correctly.
|
||||||
var Config = require('../Config');
|
var Config = require('../src/Config');
|
||||||
var Schema = require('../Schema');
|
var Schema = require('../src/Schema');
|
||||||
var dd = require('deep-diff');
|
var dd = require('deep-diff');
|
||||||
|
|
||||||
var config = new Config('test');
|
var config = new Config('test');
|
||||||
@@ -252,7 +252,7 @@ describe('Schema', () => {
|
|||||||
it('refuses to add fields with invalid pointer types', done => {
|
it('refuses to add fields with invalid pointer types', done => {
|
||||||
config.database.loadSchema()
|
config.database.loadSchema()
|
||||||
.then(schema => schema.addClassIfNotExists('NewClass', {
|
.then(schema => schema.addClassIfNotExists('NewClass', {
|
||||||
foo: {type: 'Pointer'},
|
foo: {type: 'Pointer'}
|
||||||
}))
|
}))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(135);
|
expect(error.code).toEqual(135);
|
||||||
@@ -398,7 +398,7 @@ describe('Schema', () => {
|
|||||||
config.database.loadSchema()
|
config.database.loadSchema()
|
||||||
.then(schema => schema.addClassIfNotExists('NewClass', {
|
.then(schema => schema.addClassIfNotExists('NewClass', {
|
||||||
geo1: {type: 'GeoPoint'},
|
geo1: {type: 'GeoPoint'},
|
||||||
geo2: {type: 'GeoPoint'},
|
geo2: {type: 'GeoPoint'}
|
||||||
}))
|
}))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
expect(error.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
|
||||||
|
|
||||||
var cache = require('../cache');
|
var cache = require('../src/cache');
|
||||||
var DatabaseAdapter = require('../DatabaseAdapter');
|
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var facebook = require('../facebook');
|
var facebook = require('../src/facebook');
|
||||||
var ParseServer = require('../index').ParseServer;
|
var ParseServer = require('../src/index').ParseServer;
|
||||||
|
|
||||||
var databaseURI = process.env.DATABASE_URI;
|
var databaseURI = process.env.DATABASE_URI;
|
||||||
var cloudMain = process.env.CLOUD_CODE_MAIN || './cloud/main.js';
|
var cloudMain = process.env.CLOUD_CODE_MAIN || './cloud/main.js';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var push = require('../push');
|
var push = require('../src/push');
|
||||||
|
|
||||||
describe('push', () => {
|
describe('push', () => {
|
||||||
it('can check valid master key of request', (done) => {
|
it('can check valid master key of request', (done) => {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
var Parse = require('parse/node').Parse;
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var dd = require('deep-diff');
|
var dd = require('deep-diff');
|
||||||
|
|
||||||
var hasAllPODobject = () => {
|
var hasAllPODobject = () => {
|
||||||
var obj = new Parse.Object('HasAllPOD');
|
var obj = new Parse.Object('HasAllPOD');
|
||||||
obj.set('aNumber', 5);
|
obj.set('aNumber', 5);
|
||||||
@@ -14,9 +16,9 @@ var hasAllPODobject = () => {
|
|||||||
objACL.setPublicWriteAccess(false);
|
objACL.setPublicWriteAccess(false);
|
||||||
obj.setACL(objACL);
|
obj.setACL(objACL);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
};
|
||||||
|
|
||||||
var expectedResponseForHasAllPOD = {
|
var plainOldDataSchema = {
|
||||||
className: 'HasAllPOD',
|
className: 'HasAllPOD',
|
||||||
fields: {
|
fields: {
|
||||||
//Default fields
|
//Default fields
|
||||||
@@ -33,10 +35,10 @@ var expectedResponseForHasAllPOD = {
|
|||||||
aArray: {type: 'Array'},
|
aArray: {type: 'Array'},
|
||||||
aGeoPoint: {type: 'GeoPoint'},
|
aGeoPoint: {type: 'GeoPoint'},
|
||||||
aFile: {type: 'File'}
|
aFile: {type: 'File'}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var expectedResponseforHasPointersAndRelations = {
|
var pointersAndRelationsSchema = {
|
||||||
className: 'HasPointersAndRelations',
|
className: 'HasPointersAndRelations',
|
||||||
fields: {
|
fields: {
|
||||||
//Default fields
|
//Default fields
|
||||||
@@ -56,17 +58,30 @@ var expectedResponseforHasPointersAndRelations = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var noAuthHeaders = {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
};
|
||||||
|
|
||||||
|
var restKeyHeaders = {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
};
|
||||||
|
|
||||||
|
var masterKeyHeaders = {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Master-Key': 'test',
|
||||||
|
};
|
||||||
|
|
||||||
describe('schemas', () => {
|
describe('schemas', () => {
|
||||||
it('requires the master key to get all schemas', (done) => {
|
it('requires the master key to get all schemas', (done) => {
|
||||||
request.get({
|
request.get({
|
||||||
url: 'http://localhost:8378/1/schemas',
|
url: 'http://localhost:8378/1/schemas',
|
||||||
json: true,
|
json: true,
|
||||||
headers: {
|
headers: noAuthHeaders,
|
||||||
'X-Parse-Application-Id': 'test',
|
|
||||||
'X-Parse-REST-API-Key': 'rest',
|
|
||||||
},
|
|
||||||
}, (error, response, body) => {
|
}, (error, response, body) => {
|
||||||
expect(response.statusCode).toEqual(401);
|
//api.parse.com uses status code 401, but due to the lack of keys
|
||||||
|
//being necessary in parse-server, 403 makes more sense
|
||||||
|
expect(response.statusCode).toEqual(403);
|
||||||
expect(body.error).toEqual('unauthorized');
|
expect(body.error).toEqual('unauthorized');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -76,10 +91,7 @@ describe('schemas', () => {
|
|||||||
request.get({
|
request.get({
|
||||||
url: 'http://localhost:8378/1/schemas/SomeSchema',
|
url: 'http://localhost:8378/1/schemas/SomeSchema',
|
||||||
json: true,
|
json: true,
|
||||||
headers: {
|
headers: restKeyHeaders,
|
||||||
'X-Parse-Application-Id': 'test',
|
|
||||||
'X-Parse-REST-API-Key': 'rest',
|
|
||||||
},
|
|
||||||
}, (error, response, body) => {
|
}, (error, response, body) => {
|
||||||
expect(response.statusCode).toEqual(401);
|
expect(response.statusCode).toEqual(401);
|
||||||
expect(body.error).toEqual('unauthorized');
|
expect(body.error).toEqual('unauthorized');
|
||||||
@@ -87,14 +99,23 @@ describe('schemas', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('asks for the master key if you use the rest key', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
json: true,
|
||||||
|
headers: restKeyHeaders,
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(401);
|
||||||
|
expect(body.error).toEqual('master key not specified');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('responds with empty list when there are no schemas', done => {
|
it('responds with empty list when there are no schemas', done => {
|
||||||
request.get({
|
request.get({
|
||||||
url: 'http://localhost:8378/1/schemas',
|
url: 'http://localhost:8378/1/schemas',
|
||||||
json: true,
|
json: true,
|
||||||
headers: {
|
headers: masterKeyHeaders,
|
||||||
'X-Parse-Application-Id': 'test',
|
|
||||||
'X-Parse-Master-Key': 'test',
|
|
||||||
},
|
|
||||||
}, (error, response, body) => {
|
}, (error, response, body) => {
|
||||||
expect(body.results).toEqual([]);
|
expect(body.results).toEqual([]);
|
||||||
done();
|
done();
|
||||||
@@ -113,13 +134,10 @@ describe('schemas', () => {
|
|||||||
request.get({
|
request.get({
|
||||||
url: 'http://localhost:8378/1/schemas',
|
url: 'http://localhost:8378/1/schemas',
|
||||||
json: true,
|
json: true,
|
||||||
headers: {
|
headers: masterKeyHeaders,
|
||||||
'X-Parse-Application-Id': 'test',
|
|
||||||
'X-Parse-Master-Key': 'test',
|
|
||||||
},
|
|
||||||
}, (error, response, body) => {
|
}, (error, response, body) => {
|
||||||
var expected = {
|
var expected = {
|
||||||
results: [expectedResponseForHasAllPOD,expectedResponseforHasPointersAndRelations]
|
results: [plainOldDataSchema,pointersAndRelationsSchema]
|
||||||
};
|
};
|
||||||
expect(body).toEqual(expected);
|
expect(body).toEqual(expected);
|
||||||
done();
|
done();
|
||||||
@@ -133,12 +151,9 @@ describe('schemas', () => {
|
|||||||
request.get({
|
request.get({
|
||||||
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
||||||
json: true,
|
json: true,
|
||||||
headers: {
|
headers: masterKeyHeaders,
|
||||||
'X-Parse-Application-Id': 'test',
|
|
||||||
'X-Parse-Master-Key': 'test',
|
|
||||||
},
|
|
||||||
}, (error, response, body) => {
|
}, (error, response, body) => {
|
||||||
expect(body).toEqual(expectedResponseForHasAllPOD);
|
expect(body).toEqual(plainOldDataSchema);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -150,10 +165,7 @@ describe('schemas', () => {
|
|||||||
request.get({
|
request.get({
|
||||||
url: 'http://localhost:8378/1/schemas/HASALLPOD',
|
url: 'http://localhost:8378/1/schemas/HASALLPOD',
|
||||||
json: true,
|
json: true,
|
||||||
headers: {
|
headers: masterKeyHeaders,
|
||||||
'X-Parse-Application-Id': 'test',
|
|
||||||
'X-Parse-Master-Key': 'test',
|
|
||||||
},
|
|
||||||
}, (error, response, body) => {
|
}, (error, response, body) => {
|
||||||
expect(response.statusCode).toEqual(400);
|
expect(response.statusCode).toEqual(400);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
@@ -164,4 +176,146 @@ describe('schemas', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('requires the master key to create a schema', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
json: true,
|
||||||
|
headers: noAuthHeaders,
|
||||||
|
body: {
|
||||||
|
className: 'MyClass',
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(403);
|
||||||
|
expect(body.error).toEqual('unauthorized');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('asks for the master key if you use the rest key', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
json: true,
|
||||||
|
headers: restKeyHeaders,
|
||||||
|
body: {
|
||||||
|
className: 'MyClass',
|
||||||
|
},
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(401);
|
||||||
|
expect(body.error).toEqual('master key not specified');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends an error if you use mismatching class names', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/A',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: 'B',
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(400);
|
||||||
|
expect(body).toEqual({
|
||||||
|
code: Parse.Error.INVALID_CLASS_NAME,
|
||||||
|
error: 'class name mismatch between B and A',
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends an error if you use no class name', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {},
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(400);
|
||||||
|
expect(body).toEqual({
|
||||||
|
code: 135,
|
||||||
|
error: 'POST /schemas needs class name',
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends an error if you try to create the same class twice', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: 'A',
|
||||||
|
},
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toEqual(null);
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: 'A',
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(400);
|
||||||
|
expect(body).toEqual({
|
||||||
|
code: Parse.Error.INVALID_CLASS_NAME,
|
||||||
|
error: 'class A already exists',
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds with all fields when you create a class', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: "NewClass",
|
||||||
|
fields: {
|
||||||
|
foo: {type: 'Number'},
|
||||||
|
ptr: {type: 'Pointer', targetClass: 'SomeClass'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body).toEqual({
|
||||||
|
className: 'NewClass',
|
||||||
|
fields: {
|
||||||
|
ACL: {type: 'ACL'},
|
||||||
|
createdAt: {type: 'Date'},
|
||||||
|
updatedAt: {type: 'Date'},
|
||||||
|
objectId: {type: 'String'},
|
||||||
|
foo: {type: 'Number'},
|
||||||
|
ptr: {type: 'Pointer', targetClass: 'SomeClass'},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lets you specify class name in both places', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: "NewClass",
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body).toEqual({
|
||||||
|
className: 'NewClass',
|
||||||
|
fields: {
|
||||||
|
ACL: {type: 'ACL'},
|
||||||
|
createdAt: {type: 'Date'},
|
||||||
|
updatedAt: {type: 'Date'},
|
||||||
|
objectId: {type: 'String'},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"*spec.js"
|
"*spec.js"
|
||||||
],
|
],
|
||||||
"helpers": [
|
"helpers": [
|
||||||
|
"../node_modules/babel-core/register.js",
|
||||||
"helper.js"
|
"helper.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// These tests are unit tests designed to only test transform.js.
|
// These tests are unit tests designed to only test transform.js.
|
||||||
|
|
||||||
var transform = require('../transform');
|
var transform = require('../src/transform');
|
||||||
|
|
||||||
var dummySchema = {
|
var dummySchema = {
|
||||||
data: {},
|
data: {},
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ APNS.prototype.send = function(data, deviceTokens) {
|
|||||||
var generateNotification = function(coreData, expirationTime) {
|
var generateNotification = function(coreData, expirationTime) {
|
||||||
var notification = new apn.notification();
|
var notification = new apn.notification();
|
||||||
var payload = {};
|
var payload = {};
|
||||||
for (key in coreData) {
|
for (var key in coreData) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'alert':
|
case 'alert':
|
||||||
notification.setAlertText(coreData.alert);
|
notification.setAlertText(coreData.alert);
|
||||||
@@ -34,21 +34,8 @@ ExportAdapter.prototype.connect = function() {
|
|||||||
return this.connectionPromise;
|
return this.connectionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
//http://regexr.com/3cncm
|
|
||||||
if (!this.mongoURI.match(/^mongodb:\/\/((.+):(.+)@)?([^:@]+):{0,1}([^:]+)\/(.+?)$/gm)) {
|
|
||||||
throw new Error("Invalid mongoURI: " + this.mongoURI)
|
|
||||||
}
|
|
||||||
var usernameStart = this.mongoURI.indexOf('://') + 3;
|
|
||||||
var lastAtIndex = this.mongoURI.lastIndexOf('@');
|
|
||||||
var encodedMongoURI = this.mongoURI;
|
|
||||||
var split = null;
|
|
||||||
if (lastAtIndex > 0) {
|
|
||||||
split = this.mongoURI.slice(usernameStart, lastAtIndex).split(':');
|
|
||||||
encodedMongoURI = this.mongoURI.slice(0, usernameStart) + encodeURIComponent(split[0]) + ':' + encodeURIComponent(split[1]) + this.mongoURI.slice(lastAtIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connectionPromise = Promise.resolve().then(() => {
|
this.connectionPromise = Promise.resolve().then(() => {
|
||||||
return MongoClient.connect(encodedMongoURI, {uri_decode_auth:true});
|
return MongoClient.connect(this.mongoURI);
|
||||||
}).then((db) => {
|
}).then((db) => {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
});
|
});
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
var transform = require('./transform');
|
var transform = require('./transform');
|
||||||
|
|
||||||
defaultColumns = {
|
var defaultColumns = {
|
||||||
// Contain the default columns for every parse object type (except _Join collection)
|
// Contain the default columns for every parse object type (except _Join collection)
|
||||||
_Default: {
|
_Default: {
|
||||||
"objectId": {type:'String'},
|
"objectId": {type:'String'},
|
||||||
@@ -43,13 +43,13 @@ defaultColumns = {
|
|||||||
"GCMSenderId": {type:'String'},
|
"GCMSenderId": {type:'String'},
|
||||||
"timeZone": {type:'String'},
|
"timeZone": {type:'String'},
|
||||||
"localeIdentifier": {type:'String'},
|
"localeIdentifier": {type:'String'},
|
||||||
"badge": {type:'Number'},
|
"badge": {type:'Number'}
|
||||||
},
|
},
|
||||||
// The additional default columns for the _User collection (in addition to DefaultCols)
|
// The additional default columns for the _User collection (in addition to DefaultCols)
|
||||||
_Role: {
|
_Role: {
|
||||||
"name": {type:'String'},
|
"name": {type:'String'},
|
||||||
"users": {type:'Relation',className:'_User'},
|
"users": {type:'Relation',className:'_User'},
|
||||||
"roles": {type:'Relation',className:'_Role'},
|
"roles": {type:'Relation',className:'_Role'}
|
||||||
},
|
},
|
||||||
// The additional default columns for the _User collection (in addition to DefaultCols)
|
// The additional default columns for the _User collection (in addition to DefaultCols)
|
||||||
_Session: {
|
_Session: {
|
||||||
@@ -58,12 +58,9 @@ defaultColumns = {
|
|||||||
"installationId": {type:'String'},
|
"installationId": {type:'String'},
|
||||||
"sessionToken": {type:'String'},
|
"sessionToken": {type:'String'},
|
||||||
"expiresAt": {type:'Date'},
|
"expiresAt": {type:'Date'},
|
||||||
"createdWith": {type:'Object'},
|
"createdWith": {type:'Object'}
|
||||||
},
|
}
|
||||||
_GlobalConfig: {
|
};
|
||||||
"params": {type:'Object'}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid classes must:
|
// Valid classes must:
|
||||||
// Be one of _User, _Installation, _Role, _Session OR
|
// Be one of _User, _Installation, _Role, _Session OR
|
||||||
@@ -224,7 +221,7 @@ Schema.prototype.addClassIfNotExists = function(className, fields) {
|
|||||||
error: invalidClassNameMessage(className),
|
error: invalidClassNameMessage(className),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (fieldName in fields) {
|
for (var fieldName in fields) {
|
||||||
if (!fieldNameIsValid(fieldName)) {
|
if (!fieldNameIsValid(fieldName)) {
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
code: Parse.Error.INVALID_KEY_NAME,
|
code: Parse.Error.INVALID_KEY_NAME,
|
||||||
@@ -243,18 +240,18 @@ Schema.prototype.addClassIfNotExists = function(className, fields) {
|
|||||||
_id: className,
|
_id: className,
|
||||||
objectId: 'string',
|
objectId: 'string',
|
||||||
updatedAt: 'string',
|
updatedAt: 'string',
|
||||||
createdAt: 'string',
|
createdAt: 'string'
|
||||||
};
|
};
|
||||||
for (fieldName in defaultColumns[className]) {
|
for (var fieldName in defaultColumns[className]) {
|
||||||
validatedField = schemaAPITypeToMongoFieldType(defaultColumns[className][fieldName]);
|
var validatedField = schemaAPITypeToMongoFieldType(defaultColumns[className][fieldName]);
|
||||||
if (validatedField.code) {
|
if (validatedField.code) {
|
||||||
return Promise.reject(validatedField);
|
return Promise.reject(validatedField);
|
||||||
}
|
}
|
||||||
mongoObject[fieldName] = validatedField.result;
|
mongoObject[fieldName] = validatedField.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (fieldName in fields) {
|
for (var fieldName in fields) {
|
||||||
validatedField = schemaAPITypeToMongoFieldType(fields[fieldName]);
|
var validatedField = schemaAPITypeToMongoFieldType(fields[fieldName]);
|
||||||
if (validatedField.code) {
|
if (validatedField.code) {
|
||||||
return Promise.reject(validatedField);
|
return Promise.reject(validatedField);
|
||||||
}
|
}
|
||||||
@@ -262,7 +259,6 @@ Schema.prototype.addClassIfNotExists = function(className, fields) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var geoPoints = Object.keys(mongoObject).filter(key => mongoObject[key] === 'geopoint');
|
var geoPoints = Object.keys(mongoObject).filter(key => mongoObject[key] === 'geopoint');
|
||||||
|
|
||||||
if (geoPoints.length > 1) {
|
if (geoPoints.length > 1) {
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
code: Parse.Error.INCORRECT_TYPE,
|
code: Parse.Error.INCORRECT_TYPE,
|
||||||
@@ -281,7 +277,7 @@ Schema.prototype.addClassIfNotExists = function(className, fields) {
|
|||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
// Returns a promise that resolves successfully to the new schema
|
// Returns a promise that resolves successfully to the new schema
|
||||||
// object or fails with a reason.
|
// object or fails with a reason.
|
||||||
@@ -42,7 +42,7 @@ function handleFind(req) {
|
|||||||
req.params.className, body.where, options)
|
req.params.className, body.where, options)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response && response.results) {
|
if (response && response.results) {
|
||||||
for (result of response.results) {
|
for (var result of response.results) {
|
||||||
if (result.sessionToken) {
|
if (result.sessionToken) {
|
||||||
result.sessionToken = req.info.sessionToken || result.sessionToken;
|
result.sessionToken = req.info.sessionToken || result.sessionToken;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
// schemas.js
|
// schemas.js
|
||||||
|
|
||||||
var express = require('express'),
|
var express = require('express'),
|
||||||
PromiseRouter = require('./PromiseRouter');
|
Parse = require('parse/node').Parse,
|
||||||
|
PromiseRouter = require('./PromiseRouter'),
|
||||||
|
Schema = require('./Schema');
|
||||||
|
|
||||||
var router = new PromiseRouter();
|
var router = new PromiseRouter();
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ function mongoFieldTypeToSchemaAPIType(type) {
|
|||||||
case 'string': return {type: 'String'};
|
case 'string': return {type: 'String'};
|
||||||
case 'boolean': return {type: 'Boolean'};
|
case 'boolean': return {type: 'Boolean'};
|
||||||
case 'date': return {type: 'Date'};
|
case 'date': return {type: 'Date'};
|
||||||
|
case 'map':
|
||||||
case 'object': return {type: 'Object'};
|
case 'object': return {type: 'Object'};
|
||||||
case 'array': return {type: 'Array'};
|
case 'array': return {type: 'Array'};
|
||||||
case 'geopoint': return {type: 'GeoPoint'};
|
case 'geopoint': return {type: 'GeoPoint'};
|
||||||
@@ -31,8 +34,8 @@ function mongoFieldTypeToSchemaAPIType(type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mongoSchemaAPIResponseFields(schema) {
|
function mongoSchemaAPIResponseFields(schema) {
|
||||||
fieldNames = Object.keys(schema).filter(key => key !== '_id');
|
var fieldNames = Object.keys(schema).filter(key => key !== '_id' && key !== '_metadata');
|
||||||
response = fieldNames.reduce((obj, fieldName) => {
|
var response = fieldNames.reduce((obj, fieldName) => {
|
||||||
obj[fieldName] = mongoFieldTypeToSchemaAPIType(schema[fieldName])
|
obj[fieldName] = mongoFieldTypeToSchemaAPIType(schema[fieldName])
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
@@ -54,7 +57,7 @@ function getAllSchemas(req) {
|
|||||||
if (!req.auth.isMaster) {
|
if (!req.auth.isMaster) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
status: 401,
|
status: 401,
|
||||||
response: {error: 'unauthorized'},
|
response: {error: 'master key not specified'},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return req.config.database.collection('_SCHEMA')
|
return req.config.database.collection('_SCHEMA')
|
||||||
@@ -83,7 +86,46 @@ function getOneSchema(req) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createSchema(req) {
|
||||||
|
if (!req.auth.isMaster) {
|
||||||
|
return Promise.resolve({
|
||||||
|
status: 401,
|
||||||
|
response: {error: 'master key not specified'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (req.params.className && req.body.className) {
|
||||||
|
if (req.params.className != req.body.className) {
|
||||||
|
return Promise.resolve({
|
||||||
|
status: 400,
|
||||||
|
response: {
|
||||||
|
code: Parse.Error.INVALID_CLASS_NAME,
|
||||||
|
error: 'class name mismatch between ' + req.body.className + ' and ' + req.params.className,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var className = req.params.className || req.body.className;
|
||||||
|
if (!className) {
|
||||||
|
return Promise.resolve({
|
||||||
|
status: 400,
|
||||||
|
response: {
|
||||||
|
code: 135,
|
||||||
|
error: 'POST ' + req.path + ' needs class name',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return req.config.database.loadSchema()
|
||||||
|
.then(schema => schema.addClassIfNotExists(className, req.body.fields))
|
||||||
|
.then(result => ({ response: mongoSchemaToSchemaAPIResponse(result) }))
|
||||||
|
.catch(error => ({
|
||||||
|
status: 400,
|
||||||
|
response: error,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
router.route('GET', '/schemas', getAllSchemas);
|
router.route('GET', '/schemas', getAllSchemas);
|
||||||
router.route('GET', '/schemas/:className', getOneSchema);
|
router.route('GET', '/schemas/:className', getOneSchema);
|
||||||
|
router.route('POST', '/schemas', createSchema);
|
||||||
|
router.route('POST', '/schemas/:className', createSchema);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -21,7 +21,7 @@ var Parse = require('parse/node').Parse;
|
|||||||
// validate: true indicates that key names are to be validated.
|
// validate: true indicates that key names are to be validated.
|
||||||
//
|
//
|
||||||
// Returns an object with {key: key, value: value}.
|
// Returns an object with {key: key, value: value}.
|
||||||
function transformKeyValue(schema, className, restKey, restValue, options) {
|
export function transformKeyValue(schema, className, restKey, restValue, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
// Check if the schema is known since it's a built-in field.
|
// Check if the schema is known since it's a built-in field.
|
||||||
@@ -126,7 +126,7 @@ function transformKeyValue(schema, className, restKey, restValue, options) {
|
|||||||
|
|
||||||
if (inArray && options.query && !(restValue instanceof Array)) {
|
if (inArray && options.query && !(restValue instanceof Array)) {
|
||||||
return {
|
return {
|
||||||
key: key, value: [restValue]
|
key: key, value: { '$all' : [restValue] }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user