diff --git a/spec/MongoStorageAdapter.spec.js b/spec/MongoStorageAdapter.spec.js index 2ead5c1e..6c2666a4 100644 --- a/spec/MongoStorageAdapter.spec.js +++ b/spec/MongoStorageAdapter.spec.js @@ -49,7 +49,7 @@ describe('MongoStorageAdapter', () => { it('stores objectId in _id', done => { let adapter = new MongoStorageAdapter({ uri: databaseURI }); - adapter.createObject('Foo', {}, { objectId: 'abcde' }) + adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' }) .then(() => adapter._rawFind('Foo', {})) .then(results => { expect(results.length).toEqual(1); diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 91a3b9a3..b3486177 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -10,6 +10,7 @@ var request = require('request'); var passwordCrypto = require('../src/password'); var Config = require('../src/Config'); +const rp = require('request-promise'); function verifyACL(user) { const ACL = user.getACL(); @@ -2131,7 +2132,7 @@ describe('Parse.User testing', () => { let database = new Config(Parse.applicationId).database; database.create('_User', { username: 'user', - password: '$2a$10$8/wZJyEuiEaobBBqzTG.jeY.XSFJd0rzaN//ososvEI4yLqI.4aie', + _hashed_password: '$2a$10$8/wZJyEuiEaobBBqzTG.jeY.XSFJd0rzaN//ososvEI4yLqI.4aie', _auth_data_facebook: null }, {}).then(() => { return new Promise((resolve, reject) => { @@ -2258,42 +2259,43 @@ describe('Parse.User testing', () => { }); it('should fail to become user with expired token', (done) => { - Parse.User.signUp("auser", "somepass", null, { - success: function(user) { - request.get({ - url: 'http://localhost:8378/1/classes/_Session', - json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, - }, (error, response, body) => { - var id = body.results[0].objectId; - var expiresAt = new Date((new Date()).setYear(2015)); - var token = body.results[0].sessionToken; - request.put({ - url: "http://localhost:8378/1/classes/_Session/" + id, - json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, - body: { - expiresAt: { __type: "Date", iso: expiresAt.toISOString() }, - }, - }, (error, response, body) => { - Parse.User.become(token) - .then(() => { fail("Should not have succeded"); }) - .fail((err) => { - expect(err.code).toEqual(209); - expect(err.message).toEqual("Session token is expired."); - Parse.User.logOut() // Logout to prevent polluting CLI with messages - .then(done); - }); - }); - }); - } - }); + let token; + Parse.User.signUp("auser", "somepass", null) + .then(user => rp({ + method: 'GET', + url: 'http://localhost:8378/1/classes/_Session', + json: true, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test', + }, + })) + .then(body => { + var id = body.results[0].objectId; + var expiresAt = new Date((new Date()).setYear(2015)); + token = body.results[0].sessionToken; + return rp({ + method: 'PUT', + url: "http://localhost:8378/1/classes/_Session/" + id, + json: true, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test', + }, + body: { + expiresAt: { __type: "Date", iso: expiresAt.toISOString() }, + }, + }) + }) + .then(() => Parse.User.become(token)) + .then(() => { + fail("Should not have succeded") + done(); + }, error => { + expect(error.code).toEqual(209); + expect(error.message).toEqual("Session token is expired."); + done(); + }) }); it('should not create extraneous session tokens', (done) => { diff --git a/spec/PointerPermissions.spec.js b/spec/PointerPermissions.spec.js index d78b5b6e..4ac5f8c8 100644 --- a/spec/PointerPermissions.spec.js +++ b/spec/PointerPermissions.spec.js @@ -36,8 +36,8 @@ describe('Pointer Permissions', () => { expect(res.length).toBe(1); expect(res[0].id).toBe(obj.id); done(); - }).catch((err) => { - fail('Should not fail'); + }).catch(error => { + fail(JSON.stringify(error)); done(); }); }); diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index 0af93b2d..b2b856eb 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -238,6 +238,7 @@ describe('SchemaController', () => { }); Promise.all([p1,p2]) .catch(error => { + console.log(error); expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME); expect(error.message).toEqual('Class NewClass already exists.'); done(); @@ -693,7 +694,7 @@ describe('SchemaController', () => { objectId: { type: 'String' }, updatedAt: { type: 'Date' }, createdAt: { type: 'Date' }, - ACL: { type: 'ACL' } + ACL: { type: 'ACL' }, }; expect(dd(schema.data.NewClass, expectedSchema)).toEqual(undefined); done(); diff --git a/spec/Uniqueness.spec.js b/spec/Uniqueness.spec.js index 58924954..129ec075 100644 --- a/spec/Uniqueness.spec.js +++ b/spec/Uniqueness.spec.js @@ -100,12 +100,4 @@ describe('Uniqueness', function() { done(); }); }); - - it('adding a unique index to an existing field works even if it has nulls', done => { - - }); - - it('adding a unique index to an existing field doesnt prevent you from adding new documents with nulls', done => { - - }); }); diff --git a/spec/helper.js b/spec/helper.js index a6ed4685..48b5b9ba 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -140,6 +140,12 @@ beforeEach(done => { }); afterEach(function(done) { + let afterLogOut = () => { + if (Object.keys(openConnections).length > 0) { + fail('There were open connections to the server left after the test finished'); + } + done(); + }; Parse.Cloud._removeAllHooks(); databaseAdapter.getAllClasses() .then(allSchemas => { @@ -157,16 +163,7 @@ afterEach(function(done) { }); }) .then(() => Parse.User.logOut()) - .then(() => { - if (Object.keys(openConnections).length > 0) { - fail('There were open connections to the server left after the test finished'); - } - done(); - }) - .catch(error => { - fail(JSON.stringify(error)); - done(); - }); + .then(afterLogOut, afterLogOut) }); var TestObject = Parse.Object.extend({ diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 24216ec6..c9776c7a 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -34,6 +34,12 @@ const storageAdapterAllCollections = mongoAdapter => { }); } +const convertParseSchemaToMongoSchema = ({...schema}) => { + delete schema.fields._rperm; + delete schema.fields._wperm; + return schema; +} + export class MongoStorageAdapter { // Private _uri: string; @@ -97,6 +103,7 @@ export class MongoStorageAdapter { } createClass(className, schema) { + schema = convertParseSchemaToMongoSchema(schema); return this._schemaCollection() .then(schemaCollection => schemaCollection.addSchema(className, schema.fields, schema.classLevelPermissions)); } @@ -192,6 +199,7 @@ export class MongoStorageAdapter { // and should infer from the type. Or maybe does need the schema for validations. Or maybe needs // the schem only for the legacy mongo format. We'll figure that out later. createObject(className, schema, object) { + schema = convertParseSchemaToMongoSchema(schema); const mongoObject = parseObjectToMongoObjectForCreate(className, object, schema); return this._adaptiveCollection(className) .then(collection => collection.insertOne(mongoObject)) @@ -208,6 +216,7 @@ export class MongoStorageAdapter { // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined. // If there is some other error, reject with INTERNAL_SERVER_ERROR. deleteObjectsByQuery(className, schema, query) { + schema = convertParseSchemaToMongoSchema(schema); return this._adaptiveCollection(className) .then(collection => { let mongoWhere = transformWhere(className, query, schema); @@ -225,6 +234,7 @@ export class MongoStorageAdapter { // Apply the update to all objects that match the given Parse Query. updateObjectsByQuery(className, schema, query, update) { + schema = convertParseSchemaToMongoSchema(schema); const mongoUpdate = transformUpdate(className, update, schema); const mongoWhere = transformWhere(className, query, schema); return this._adaptiveCollection(className) @@ -234,6 +244,7 @@ export class MongoStorageAdapter { // Atomically finds and updates an object based on query. // Return value not currently well specified. findOneAndUpdate(className, schema, query, update) { + schema = convertParseSchemaToMongoSchema(schema); const mongoUpdate = transformUpdate(className, update, schema); const mongoWhere = transformWhere(className, query, schema); return this._adaptiveCollection(className) @@ -243,6 +254,7 @@ export class MongoStorageAdapter { // Hopefully we can get rid of this. It's only used for config and hooks. upsertOneObject(className, schema, query, update) { + schema = convertParseSchemaToMongoSchema(schema); const mongoUpdate = transformUpdate(className, update, schema); const mongoWhere = transformWhere(className, query, schema); return this._adaptiveCollection(className) @@ -251,6 +263,7 @@ export class MongoStorageAdapter { // Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }. find(className, schema, query, { skip, limit, sort }) { + schema = convertParseSchemaToMongoSchema(schema); let mongoWhere = transformWhere(className, query, schema); let mongoSort = _.mapKeys(sort, (value, fieldName) => transformKey(className, fieldName, schema)); return this._adaptiveCollection(className) @@ -264,6 +277,7 @@ export class MongoStorageAdapter { // Way of determining if a field is nullable. Undefined doesn't count against uniqueness, // which is why we use sparse indexes. ensureUniqueness(className, schema, fieldNames) { + schema = convertParseSchemaToMongoSchema(schema); let indexCreationRequest = {}; let mongoFieldNames = fieldNames.map(fieldName => transformKey(className, fieldName, schema)); mongoFieldNames.forEach(fieldName => { @@ -287,6 +301,7 @@ export class MongoStorageAdapter { // Executs a count. count(className, schema, query) { + schema = convertParseSchemaToMongoSchema(schema); return this._adaptiveCollection(className) .then(collection => collection.count(transformWhere(className, query, schema))); } diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index bd28b990..65bea913 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -67,8 +67,8 @@ const validateQuery = query => { } Object.keys(query).forEach(key => { - if (query[key].$regex) { - if (typeof query[key].$options === 'string') {g + if (query && query[key] && query[key].$regex) { + if (typeof query[key].$options === 'string') { if (!query[key].$options.match(/^[imxs]+$/)) { throw new Parse.Error(Parse.Error.INVALID_QUERY, `Bad $options value for query: ${query[key].$options}`); } @@ -764,7 +764,7 @@ DatabaseController.prototype.deleteSchema = function(className) { }) .then(schema => { return this.collectionExists(className) - .then(exist => this.adapter.count(className)) + .then(exist => this.adapter.count(className, { fields: {} })) .then(count => { if (count > 0) { throw new Parse.Error(255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.`); diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index b5e58469..77e22f8a 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -234,6 +234,20 @@ const convertSchemaToAdapterSchema = schema => { return schema; } +const convertAdapterSchemaToParseSchema = ({...schema}) => { + delete schema.fields._rperm; + delete schema.fields._wperm; + + schema.fields.ACL = { type: 'ACL' }; + + if (schema.className === '_User') { + delete schema.fields._hashed_password; + schema.fields.password = { type: 'String' }; + } + + return schema; +} + const injectDefaultSchema = schema => ({ className: schema.className, fields: { @@ -316,6 +330,7 @@ class SchemaController { } return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields, classLevelPermissions, className })) + .then(convertAdapterSchemaToParseSchema) .catch(error => { if (error && error.code === Parse.Error.DUPLICATE_VALUE) { throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`); @@ -342,6 +357,8 @@ class SchemaController { } }); + delete existingFields._rperm; + delete existingFields._wperm; let newSchema = buildMergedSchemaObject(existingFields, submittedFields); let validationError = this.validateSchemaData(className, newSchema, classLevelPermissions); if (validationError) {