Merge branch 'master' of https://github.com/ParsePlatform/parse-server into mcdonald-gcs-adapter

* 'master' of https://github.com/ParsePlatform/parse-server:
  Remove limit when counting results.
  beforeSave changes should propagate to the response
  Fix delete relation field when _Join collection not exist
  Test empty authData block on login for #413
  Fix for related query on non-existing column
  Fix Markdown format: make checkboxes visible
  Fix create wrong _Session for Facebook login
  Modified the npm dev script to support Windows
  Improves tests, ensure unicity of roleIds
  Fix reversed roles lookup
  Fix leak warnings in tests, use mongodb-runner from node_modules
  Improves documentation, add loading tests
  Improves loading of Push Adapter, fix loading of S3Adapter
  Adds public_html and views for packaging
  Removes shebang for windows
  Better support for windows builds
  Fix add field to system schema
  Convert Schema.js to ES6 class.
This commit is contained in:
Mike McDonald
2016-03-06 15:34:40 -08:00
20 changed files with 740 additions and 469 deletions

View File

@@ -1,6 +1,8 @@
var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
var FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
var ParsePushAdapter = require("../src/Adapters/Push/ParsePushAdapter");
var S3Adapter = require("../src/Adapters/Files/S3Adapter").default;
describe("AdapterLoader", ()=>{
@@ -84,4 +86,27 @@ describe("AdapterLoader", ()=>{
}).not.toThrow("foo is required for that adapter");
done();
});
it("should load push adapter from options", (done) => {
var options = {
ios: {
bundleId: 'bundle.id'
}
}
expect(() => {
var adapter = loadAdapter(undefined, ParsePushAdapter, options);
expect(adapter.constructor).toBe(ParsePushAdapter);
expect(adapter).not.toBe(undefined);
}).not.toThrow();
done();
});
it("should load S3Adapter from direct passing", (done) => {
var s3Adapter = new S3Adapter("key", "secret", "bucket")
expect(() => {
var adapter = loadAdapter(s3Adapter, FilesAdapter);
expect(adapter).toBe(s3Adapter);
}).not.toThrow();
done();
})
});

View File

@@ -967,6 +967,23 @@ describe('miscellaneous', function() {
});
});
it('beforeSave change propagates through the save response', (done) => {
Parse.Cloud.beforeSave('ChangingObject', function(request, response) {
request.object.set('foo', 'baz');
response.success();
});
let obj = new Parse.Object('ChangingObject');
obj.save({ foo: 'bar' }).then((objAgain) => {
expect(objAgain.get('foo')).toEqual('baz');
Parse.Cloud._removeHook("Triggers", "beforeSave", "ChangingObject");
done();
}, (e) => {
Parse.Cloud._removeHook("Triggers", "beforeSave", "ChangingObject");
fail('Should not have failed to save.');
done();
});
});
it('dedupes an installation properly and returns updatedAt', (done) => {
let headers = {
'Content-Type': 'application/json',
@@ -995,4 +1012,32 @@ describe('miscellaneous', function() {
});
});
it('android login providing empty authData block works', (done) => {
let headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
let data = {
username: 'pulse1989',
password: 'password1234',
authData: {}
};
let requestOptions = {
headers: headers,
url: 'http://localhost:8378/1/users',
body: JSON.stringify(data)
};
request.post(requestOptions, (error, response, body) => {
expect(error).toBe(null);
requestOptions.url = 'http://localhost:8378/1/login';
request.get(requestOptions, (error, response, body) => {
expect(error).toBe(null);
let b = JSON.parse(body);
expect(typeof b['sessionToken']).toEqual('string');
done();
});
});
});
});

View File

@@ -1,4 +1,4 @@
"use strict";
// Roles are not accessible without the master key, so they are not intended
// for use by clients. We can manually test them using the master key.
@@ -64,26 +64,30 @@ describe('Parse Role testing', () => {
var rolesNames = ["FooRole", "BarRole", "BazRole"];
var createRole = function(name, parent, user) {
var createRole = function(name, sibling, user) {
var role = new Parse.Role(name, new Parse.ACL());
if (user) {
var users = role.relation('users');
users.add(user);
}
if (parent) {
role.relation('roles').add(parent);
if (sibling) {
role.relation('roles').add(sibling);
}
return role.save({}, { useMasterKey: true });
}
var roleIds = {};
createTestUser().then( (user) => {
return createRole(rolesNames[0], null, null).then( (aRole) => {
// Put the user on the 1st role
return createRole(rolesNames[0], null, user).then( (aRole) => {
roleIds[aRole.get("name")] = aRole.id;
// set the 1st role as a sibling of the second
// user will should have 2 role now
return createRole(rolesNames[1], aRole, null);
}).then( (anotherRole) => {
roleIds[anotherRole.get("name")] = anotherRole.id;
return createRole(rolesNames[2], anotherRole, user);
// set this role as a sibling of the last
// the user should now have 3 roles
return createRole(rolesNames[2], anotherRole, null);
}).then( (lastRole) => {
roleIds[lastRole.get("name")] = lastRole.id;
var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
@@ -118,6 +122,80 @@ describe('Parse Role testing', () => {
});
});
});
it("Should properly resolve roles", (done) => {
let admin = new Parse.Role("Admin", new Parse.ACL());
let moderator = new Parse.Role("Moderator", new Parse.ACL());
let superModerator = new Parse.Role("SuperModerator", new Parse.ACL());
let contentManager = new Parse.Role('ContentManager', new Parse.ACL());
let superContentManager = new Parse.Role('SuperContentManager', new Parse.ACL());
Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true}).then(() => {
contentManager.getRoles().add([moderator, superContentManager]);
moderator.getRoles().add([admin, superModerator]);
superContentManager.getRoles().add(superModerator);
return Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true});
}).then(() => {
var auth = new Auth({ config: new Config("test"), isMaster: true });
// For each role, fetch their sibling, what they inherit
// return with result and roleId for later comparison
let promises = [admin, moderator, contentManager, superModerator].map((role) => {
return auth._getAllRoleNamesForId(role.id).then((result) => {
return Parse.Promise.as({
id: role.id,
name: role.get('name'),
roleIds: result
});
})
});
return Parse.Promise.when(promises);
}).then((results) => {
results.forEach((result) => {
let id = result.id;
let roleIds = result.roleIds;
if (id == admin.id) {
expect(roleIds.length).toBe(2);
expect(roleIds.indexOf(moderator.id)).not.toBe(-1);
expect(roleIds.indexOf(contentManager.id)).not.toBe(-1);
} else if (id == moderator.id) {
expect(roleIds.length).toBe(1);
expect(roleIds.indexOf(contentManager.id)).toBe(0);
} else if (id == contentManager.id) {
expect(roleIds.length).toBe(0);
} else if (id == superModerator.id) {
expect(roleIds.length).toBe(3);
expect(roleIds.indexOf(moderator.id)).not.toBe(-1);
expect(roleIds.indexOf(contentManager.id)).not.toBe(-1);
expect(roleIds.indexOf(superContentManager.id)).not.toBe(-1);
}
});
done();
}).fail((err) => {
console.error(err);
done();
})
});
it('can create role and query empty users', (done)=> {
var roleACL = new Parse.ACL();
roleACL.setPublicReadAccess(true);
var role = new Parse.Role('subscribers', roleACL);
role.save({}, {useMasterKey : true})
.then((x)=>{
var query = role.relation('users').query();
query.find({useMasterKey : true})
.then((users)=>{
done();
}, (e)=>{
fail('should not have errors');
done();
});
}, (e) => {
console.log(e);
fail('should not have errored');
});
});
});

View File

@@ -175,17 +175,26 @@ describe('rest create', () => {
}
}
};
var newUserSignedUpByFacebookObjectId;
rest.create(config, auth.nobody(config), '_User', data)
.then((r) => {
expect(typeof r.response.objectId).toEqual('string');
expect(typeof r.response.createdAt).toEqual('string');
expect(typeof r.response.sessionToken).toEqual('string');
newUserSignedUpByFacebookObjectId = r.response.objectId;
return rest.create(config, auth.nobody(config), '_User', data);
}).then((r) => {
expect(typeof r.response.objectId).toEqual('string');
expect(typeof r.response.createdAt).toEqual('string');
expect(typeof r.response.username).toEqual('string');
expect(typeof r.response.updatedAt).toEqual('string');
expect(r.response.objectId).toEqual(newUserSignedUpByFacebookObjectId);
return rest.find(config, auth.master(config),
'_Session', {sessionToken: r.response.sessionToken});
}).then((response) => {
expect(response.results.length).toEqual(1);
var output = response.results[0];
expect(output.user.objectId).toEqual(newUserSignedUpByFacebookObjectId);
done();
});
});

View File

@@ -180,20 +180,18 @@ describe('Schema', () => {
it('will fail to create a class if that class was already created by an object', done => {
config.database.loadSchema()
.then(schema => {
schema.validateObject('NewClass', {foo: 7})
.then(() => {
schema.reload()
.then(schema => schema.addClassIfNotExists('NewClass', {
foo: {type: 'String'}
}))
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
expect(error.message).toEqual('Class NewClass already exists.');
done();
});
.then(schema => {
schema.validateObject('NewClass', { foo: 7 })
.then(() => schema.reloadData())
.then(() => schema.addClassIfNotExists('NewClass', {
foo: { type: 'String' }
}))
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
expect(error.message).toEqual('Class NewClass already exists.');
done();
});
});
})
});
it('will resolve class creation races appropriately', done => {
@@ -579,6 +577,38 @@ describe('Schema', () => {
});
});
it('can delete relation field when related _Join collection not exist', done => {
config.database.loadSchema()
.then(schema => {
schema.addClassIfNotExists('NewClass', {
relationField: {type: 'Relation', targetClass: '_User'}
})
.then(mongoObj => {
expect(mongoObj).toEqual({
_id: 'NewClass',
objectId: 'string',
updatedAt: 'string',
createdAt: 'string',
relationField: 'relation<_User>',
});
})
.then(() => config.database.collectionExists('_Join:relationField:NewClass'))
.then(exist => {
expect(exist).toEqual(false);
})
.then(() => schema.deleteField('relationField', 'NewClass', config.database))
.then(() => schema.reloadData())
.then(() => {
expect(schema['data']['NewClass']).toEqual({
objectId: 'string',
updatedAt: 'string',
createdAt: 'string'
});
done();
});
});
});
it('can delete string fields and resave as number field', done => {
Parse.Object.disableSingleInstance();
var obj1 = hasAllPODobject();

View File

@@ -561,6 +561,63 @@ describe('schemas', () => {
})
});
it('lets you add fields to system schema', done => {
request.post({
url: 'http://localhost:8378/1/schemas/_User',
headers: masterKeyHeaders,
json: true
}, (error, response, body) => {
request.put({
url: 'http://localhost:8378/1/schemas/_User',
headers: masterKeyHeaders,
json: true,
body: {
fields: {
newField: {type: 'String'}
}
}
}, (error, response, body) => {
expect(body).toEqual({
className: '_User',
fields: {
objectId: {type: 'String'},
updatedAt: {type: 'Date'},
createdAt: {type: 'Date'},
username: {type: 'String'},
password: {type: 'String'},
authData: {type: 'Object'},
email: {type: 'String'},
emailVerified: {type: 'Boolean'},
newField: {type: 'String'},
ACL: {type: 'ACL'}
}
});
request.get({
url: 'http://localhost:8378/1/schemas/_User',
headers: masterKeyHeaders,
json: true
}, (error, response, body) => {
expect(body).toEqual({
className: '_User',
fields: {
objectId: {type: 'String'},
updatedAt: {type: 'Date'},
createdAt: {type: 'Date'},
username: {type: 'String'},
password: {type: 'String'},
authData: {type: 'Object'},
email: {type: 'String'},
emailVerified: {type: 'Boolean'},
newField: {type: 'String'},
ACL: {type: 'ACL'}
}
});
done();
});
});
})
});
it('lets you delete multiple fields and add fields', done => {
var obj1 = hasAllPODobject();
obj1.save()