From 039d9874dc91190266fd38c2763c6b7443b09ef1 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 10 Oct 2017 07:55:39 -0500 Subject: [PATCH 1/7] Support for Parse Schema --- integration/test/ParseSchemaTest.js | 247 ++++++++++++++++++++++++++ src/CoreManager.js | 17 ++ src/Parse.js | 1 + src/ParseSchema.js | 259 ++++++++++++++++++++++++++++ 4 files changed, 524 insertions(+) create mode 100644 integration/test/ParseSchemaTest.js create mode 100644 src/ParseSchema.js diff --git a/integration/test/ParseSchemaTest.js b/integration/test/ParseSchemaTest.js new file mode 100644 index 000000000..79bcf9a5e --- /dev/null +++ b/integration/test/ParseSchemaTest.js @@ -0,0 +1,247 @@ +const assert = require('assert'); +const clear = require('./clear'); +const Parse = require('../../node'); + +describe('Schema', () => { + before(() => { + Parse.initialize('integration'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.CoreManager.set('MASTER_KEY', 'notsosecret'); + Parse.Storage._clear(); + }); + + beforeEach((done) => { + clear().then(() => { + done(); + }); + }); + + it('invalid get all no schema', (done) => { + Parse.Schema.all().then(() => {}).fail((e) => { + done(); + }); + }); + + it('invalid get no schema', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + testSchema.get().then(() => {}).fail((e) => { + done(); + }); + }); + + it('save', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + testSchema.save().then((result) => { + assert.equal(result.className, 'SchemaTest'); + done(); + }); + }); + + it('get', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + testSchema + .addField('defaultFieldString') + .addString('stringField') + .addNumber('numberField') + .addBoolean('booleanField') + .addDate('dateField') + .addFile('fileField') + .addGeoPoint('geoPointField') + .addArray('arrayField') + .addObject('objectField') + .addPointer('pointerField', '_User') + .addRelation('relationField', '_User'); + + testSchema.save().then(() => { + return testSchema.get(); + }).then((result) => { + assert.equal(result.fields.defaultFieldString.type, 'String'); + assert.equal(result.fields.stringField.type, 'String'); + assert.equal(result.fields.numberField.type, 'Number'); + assert.equal(result.fields.booleanField.type, 'Boolean'); + assert.equal(result.fields.dateField.type, 'Date'); + assert.equal(result.fields.fileField.type, 'File'); + assert.equal(result.fields.geoPointField.type, 'GeoPoint'); + assert.equal(result.fields.arrayField.type, 'Array'); + assert.equal(result.fields.objectField.type, 'Object'); + assert.equal(result.fields.pointerField.type, 'Pointer'); + assert.equal(result.fields.relationField.type, 'Relation'); + assert.equal(result.fields.pointerField.targetClass, '_User'); + assert.equal(result.fields.relationField.targetClass, '_User'); + done(); + }); + }); + + it('all', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + testSchema.save().then(() => { + return Parse.Schema.all(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('update', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + testSchema.addString('name'); + testSchema.save().then(() => { + testSchema.deleteField('name'); + testSchema.addNumber('quantity'); + testSchema.addBoolean('status'); + return testSchema.update(); + }).then((result) => { + assert.equal(result.fields.status.type, 'Boolean'); + assert.equal(result.fields.quantity.type, 'Number'); + assert.equal(result.fields.name, undefined); + done(); + }); + }); + + it('delete', (done) => { + const testSchema1 = new Parse.Schema('SchemaTest1'); + const testSchema2 = new Parse.Schema('SchemaTest2'); + testSchema1.save().then(() => { + return testSchema2.save(); + }).then(() => { + return Parse.Schema.all(); + }).then((results) => { + assert.equal(results.length, 2); + return testSchema1.delete(); + }).then(() => { + return Parse.Schema.all(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].className, 'SchemaTest2'); + done(); + }); + }); + + it('save index', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + const index = { + name: 1 + }; + testSchema.addString('name'); + testSchema.addIndex('test_index', index); + testSchema.save().then((result) => { + assert.notEqual(result.indexes.test_index, undefined); + done(); + }); + }); + + it('update index', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + testSchema.save().then((result) => { + const index = { + name: 1 + }; + testSchema.addString('name'); + testSchema.addIndex('test_index', index); + return testSchema.update(); + }).then((result) => { + assert.notEqual(result.indexes.test_index, undefined); + done(); + }); + }); + + it('delete index', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + testSchema.save().then((result) => { + const index = { + name: 1 + }; + testSchema.addString('name'); + testSchema.addIndex('test_index', index); + return testSchema.update(); + }).then((result) => { + assert.notEqual(result.indexes.test_index, undefined); + testSchema.deleteIndex('test_index'); + return testSchema.update(); + }).then((result) => { + assert.equal(result.indexes, undefined); + done(); + }); + }); + + it('invalid field name', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + try { + testSchema.addField(null); + } catch (e) { + done(); + } + }); + + it('invalid field type', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + try { + testSchema.addField('name', 'UnknownType'); + } catch (e) { + done(); + } + }); + + it('invalid index name', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + try { + testSchema.addIndex(null); + } catch (e) { + done(); + } + }); + + it('invalid index', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + try { + testSchema.addIndex('name', null); + } catch (e) { + done(); + } + }); + + it('invalid pointer name', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + try { + testSchema.addPointer(null); + } catch (e) { + done(); + } + }); + + it('invalid pointer class', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + try { + testSchema.addPointer('name', null); + } catch (e) { + done(); + } + }); + + it('invalid relation name', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + try { + testSchema.addRelation(null); + } catch (e) { + done(); + } + }); + + it('invalid relation class', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + try { + testSchema.addRelation('name', null); + } catch (e) { + done(); + } + }); + + it('assert class name', (done) => { + const testSchema = new Parse.Schema(); + try { + testSchema.assertClassName(); + } catch (e) { + done(); + } + }); +}); diff --git a/src/CoreManager.js b/src/CoreManager.js index 8dac29644..383f93701 100644 --- a/src/CoreManager.js +++ b/src/CoreManager.js @@ -76,6 +76,13 @@ type RESTController = { request: (method: string, path: string, data: mixed) => ParsePromise; ajax: (method: string, url: string, data: any, headers?: any) => ParsePromise; }; +type SchemaController = { + get: (className: string, options: RequestOptions) => ParsePromise; + delete: (className: string, options: RequestOptions) => ParsePromise; + create: (className: string, params: any, options: RequestOptions) => ParsePromise; + update: (className: string, params: any, options: RequestOptions) => ParsePromise; + send(className: string, method: string, params: any, options: RequestOptions): ParsePromise; +}; type SessionController = { getSession: (token: RequestOptions) => ParsePromise; }; @@ -131,6 +138,7 @@ type Config = { PushController?: PushController, QueryController?: QueryController, RESTController?: RESTController, + SchemaController?: SchemaController, SessionController?: SessionController, StorageController?: StorageController, UserController?: UserController, @@ -285,6 +293,15 @@ module.exports = { return config['RESTController']; }, + setSchemaController(controller: SchemaController) { + requireMethods('SchemaController', ['get', 'create', 'update', 'delete', 'send'], controller); + config['SchemaController'] = controller; + }, + + getSchemaController(): SchemaController { + return config['SchemaController']; + }, + setSessionController(controller: SessionController) { requireMethods('SessionController', ['getSession'], controller); config['SessionController'] = controller; diff --git a/src/Parse.js b/src/Parse.js index 872f4ae19..4cae086bc 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -116,6 +116,7 @@ Parse.Push = require('./Push'); Parse.Query = require('./ParseQuery').default; Parse.Relation = require('./ParseRelation').default; Parse.Role = require('./ParseRole').default; +Parse.Schema = require('./ParseSchema').default; Parse.Session = require('./ParseSession').default; Parse.Storage = require('./Storage'); Parse.User = require('./ParseUser').default; diff --git a/src/ParseSchema.js b/src/ParseSchema.js new file mode 100644 index 000000000..b7ea10d05 --- /dev/null +++ b/src/ParseSchema.js @@ -0,0 +1,259 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ + +import CoreManager from './CoreManager'; +import ParsePromise from './ParsePromise'; + +import type { RequestOptions, FullOptions } from './RESTController'; + +const FIELD_TYPES = ['String', 'Number', 'Boolean', 'Date', 'File', 'GeoPoint', 'Array', 'Object', 'Pointer', 'Relation']; + +/** + * @class Parse.Schema + * @constructor + * @param {String} className The class name for the object + * + *

A Parse.Schema object is for handling schema data from Parse. + * All the schemas methods require master key.

+ */ +export default class ParseSchema { + className: string; + _fields: { [key: string]: mixed }; + _indexes: { [key: string]: mixed }; + + constructor(className: ?string) { + if (typeof className === 'string') { + if (className === 'User' && CoreManager.get('PERFORM_USER_REWRITE')) { + this.className = '_User'; + } else { + this.className = className; + } + } + + this._fields = {}; + this._indexes = {}; + } + + static all(options: FullOptions) { + options = options || {}; + const controller = CoreManager.getSchemaController(); + + return controller.get('', {}, options) + .then((response) => { + if (response.results.length === 0) { + throw new Error('Schema not found.'); + } + return response.results; + })._thenRunCallbacks(options); + } + + get(options: FullOptions) { + this.assertClassName(); + + options = options || {}; + const controller = CoreManager.getSchemaController(); + + return controller.get(this.className, options) + .then((response) => { + if (!response) { + throw new Error('Schema not found.'); + } + return response; + })._thenRunCallbacks(options); + } + + save(options: FullOptions) { + this.assertClassName(); + + options = options || {}; + const controller = CoreManager.getSchemaController(); + const params = { + className: this.className, + fields: this._fields, + indexes: this._indexes, + }; + + return controller.create(this.className, params, options) + .then((response) => { + return response; + })._thenRunCallbacks(options); + } + + update(options: FullOptions) { + this.assertClassName(); + + options = options || {}; + const controller = CoreManager.getSchemaController(); + const params = { + className: this.className, + fields: this._fields, + indexes: this._indexes, + }; + + return controller.update(this.className, params, options) + .then((response) => { + return response; + })._thenRunCallbacks(options); + } + + delete(options: FullOptions) { + this.assertClassName(); + + options = options || {}; + const controller = CoreManager.getSchemaController(); + + return controller.delete(this.className, options) + .then((response) => { + return response; + })._thenRunCallbacks(options); + } + + assertClassName() { + if (!this.className) { + throw new Error('You must set a Class Name before making any request.'); + } + } + + addField(name: string, type: string) { + type = type || 'String'; + + if (!name) { + throw new Error('field name may not be null.'); + } + if (FIELD_TYPES.indexOf(type) === -1) { + throw new Error(`${type} is not a valid type.`); + } + + this._fields[name] = { type }; + + return this; + } + + addIndex(name: string, index: any) { + if (!name) { + throw new Error('index name may not be null.'); + } + if (!index) { + throw new Error('index may not be null.'); + } + + this._indexes[name] = index; + + return this; + } + + addString(name: string) { + return this.addField(name, 'String'); + } + + addNumber(name: string) { + return this.addField(name, 'Number'); + } + + addBoolean(name: string) { + return this.addField(name, 'Boolean'); + } + + addDate(name: string) { + return this.addField(name, 'Date'); + } + + addFile(name: string) { + return this.addField(name, 'File'); + } + + addGeoPoint(name: string) { + return this.addField(name, 'GeoPoint'); + } + + addArray(name: string) { + return this.addField(name, 'Array'); + } + + addObject(name: string) { + return this.addField(name, 'Object'); + } + + addPointer(name: string, targetClass: string) { + if (!name) { + throw new Error('field name may not be null.'); + } + if (!targetClass) { + throw new Error('You need to set the targetClass of the Pointer.'); + } + + this._fields[name] = { + type: 'Pointer', + targetClass + }; + + return this; + } + + addRelation(name: string, targetClass: string) { + if (!name) { + throw new Error('field name may not be null.'); + } + if (!targetClass) { + throw new Error('You need to set the targetClass of the Relation.'); + } + + this._fields[name] = { + type: 'Relation', + targetClass + }; + + return this; + } + + deleteField(name: string) { + this._fields[name] = { __op: 'Delete'}; + } + + deleteIndex(name: string) { + this._fields = {}; + this._indexes[name] = { __op: 'Delete'}; + } +} + +const DefaultController = { + send(className: string, method: string, params: any, options: RequestOptions): ParsePromise { + const RESTController = CoreManager.getRESTController(); + const requestOptions = { useMasterKey: true }; + if (options.hasOwnProperty('sessionToken')) { + requestOptions.sessionToken = options.sessionToken; + } + return RESTController.request( + method, + `schemas/${className}`, + params, + requestOptions + ); + }, + + get(className: string, options: RequestOptions): ParsePromise { + return this.send(className, 'GET', {}, options); + }, + + create(className: string, params: any, options: RequestOptions): ParsePromise { + return this.send(className, 'POST', params, options); + }, + + update(className: string, params: any, options: RequestOptions): ParsePromise { + return this.send(className, 'PUT', params, options); + }, + + delete(className: string, options: RequestOptions): ParsePromise { + return this.send(className, 'DELETE', {}, options); + } +}; + +CoreManager.setSchemaController(DefaultController); From fadc6e77779ec8ddfbc10b1d059803c614109d3f Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 11 Oct 2017 23:17:42 -0500 Subject: [PATCH 2/7] multiple update --- integration/test/ParseSchemaTest.js | 15 +++++++++++++++ src/ParseSchema.js | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/integration/test/ParseSchemaTest.js b/integration/test/ParseSchemaTest.js index 79bcf9a5e..11bfeb1ac 100644 --- a/integration/test/ParseSchemaTest.js +++ b/integration/test/ParseSchemaTest.js @@ -98,6 +98,21 @@ describe('Schema', () => { }); }); + it('multiple update', (done) => { + const testSchema = new Parse.Schema('SchemaTest'); + testSchema.save().then(() => { + testSchema.addString('name'); + return testSchema.update(); + }).then(() => { + return testSchema.update(); + }).then(() => { + return testSchema.get(); + }).then((result) => { + assert.equal(Object.keys(result.fields).length, 5); + done(); + }); + }); + it('delete', (done) => { const testSchema1 = new Parse.Schema('SchemaTest1'); const testSchema2 = new Parse.Schema('SchemaTest2'); diff --git a/src/ParseSchema.js b/src/ParseSchema.js index b7ea10d05..8a8e7e5cb 100644 --- a/src/ParseSchema.js +++ b/src/ParseSchema.js @@ -98,6 +98,9 @@ export default class ParseSchema { indexes: this._indexes, }; + this._fields = {}; + this._indexes = {}; + return controller.update(this.className, params, options) .then((response) => { return response; @@ -219,7 +222,6 @@ export default class ParseSchema { } deleteIndex(name: string) { - this._fields = {}; this._indexes[name] = { __op: 'Delete'}; } } From 3995cca2d080f69e11d8771ec219902b8092552d Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Mon, 27 Nov 2017 16:10:12 -0600 Subject: [PATCH 3/7] test fix --- integration/package.json | 2 +- integration/test/ParseSchemaTest.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/package.json b/integration/package.json index b5a35b928..5927ccbc9 100644 --- a/integration/package.json +++ b/integration/package.json @@ -3,7 +3,7 @@ "dependencies": { "express": "^4.13.4", "mocha": "^2.4.5", - "parse-server": "^2.6.0" + "parse-server": "^2.7.0" }, "scripts": { "test": "mocha --reporter dot -t 5000" diff --git a/integration/test/ParseSchemaTest.js b/integration/test/ParseSchemaTest.js index 11bfeb1ac..91dfbffc9 100644 --- a/integration/test/ParseSchemaTest.js +++ b/integration/test/ParseSchemaTest.js @@ -174,7 +174,7 @@ describe('Schema', () => { testSchema.deleteIndex('test_index'); return testSchema.update(); }).then((result) => { - assert.equal(result.indexes, undefined); + assert.equal(result.indexes.test_index, undefined); done(); }); }); From ddf2356c64abd618871ba72af7984578d9ac401e Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Mon, 27 Nov 2017 19:10:59 -0600 Subject: [PATCH 4/7] bump node to 6.11.4 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 28561741c..fc6cec052 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ --- language: node_js node_js: -- '5' +- '6.11.4' branches: only: From a909803cc61f1414deca998b9014d1e736a3d436 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Mon, 27 Nov 2017 22:14:14 -0600 Subject: [PATCH 5/7] docs --- src/ParseSchema.js | 179 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 172 insertions(+), 7 deletions(-) diff --git a/src/ParseSchema.js b/src/ParseSchema.js index 8a8e7e5cb..4a0300daa 100644 --- a/src/ParseSchema.js +++ b/src/ParseSchema.js @@ -17,19 +17,27 @@ import type { RequestOptions, FullOptions } from './RESTController'; const FIELD_TYPES = ['String', 'Number', 'Boolean', 'Date', 'File', 'GeoPoint', 'Array', 'Object', 'Pointer', 'Relation']; /** - * @class Parse.Schema - * @constructor - * @param {String} className The class name for the object + * A Parse.Schema object is for handling schema data from Parse. + *

All the schemas methods require MasterKey. * - *

A Parse.Schema object is for handling schema data from Parse. - * All the schemas methods require master key.

+ *
+ * const schema = new Parse.Schema('MyClass');
+ * schema.addString('field');
+ * schema.addIndex('index_name', {'field', 1});
+ * schema.save();
+ * 
+ *

+ * @alias Parse.Schema */ -export default class ParseSchema { +class ParseSchema { className: string; _fields: { [key: string]: mixed }; _indexes: { [key: string]: mixed }; - constructor(className: ?string) { + /** + * @param {String} className Parse Class string. + */ + constructor(className: string) { if (typeof className === 'string') { if (className === 'User' && CoreManager.get('PERFORM_USER_REWRITE')) { this.className = '_User'; @@ -42,6 +50,21 @@ export default class ParseSchema { this._indexes = {}; } + /** + * Static method to get all schemas + * @param {Object} options A Backbone-style options object. + * Valid options are:
    + *
  • success: A Backbone-style success callback + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * + * @return {Parse.Promise} A promise that is resolved with the result when + * the query completes. + */ static all(options: FullOptions) { options = options || {}; const controller = CoreManager.getSchemaController(); @@ -55,6 +78,21 @@ export default class ParseSchema { })._thenRunCallbacks(options); } + /** + * Get the Schema from Parse + * @param {Object} options A Backbone-style options object. + * Valid options are:
    + *
  • success: A Backbone-style success callback + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * + * @return {Parse.Promise} A promise that is resolved with the result when + * the query completes. + */ get(options: FullOptions) { this.assertClassName(); @@ -70,6 +108,21 @@ export default class ParseSchema { })._thenRunCallbacks(options); } + /** + * Create a new Schema on Parse + * @param {Object} options A Backbone-style options object. + * Valid options are:
    + *
  • success: A Backbone-style success callback + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * + * @return {Parse.Promise} A promise that is resolved with the result when + * the query completes. + */ save(options: FullOptions) { this.assertClassName(); @@ -87,6 +140,21 @@ export default class ParseSchema { })._thenRunCallbacks(options); } + /** + * Update a Schema from Parse + * @param {Object} options A Backbone-style options object. + * Valid options are:
    + *
  • success: A Backbone-style success callback + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * + * @return {Parse.Promise} A promise that is resolved with the result when + * the query completes. + */ update(options: FullOptions) { this.assertClassName(); @@ -107,6 +175,21 @@ export default class ParseSchema { })._thenRunCallbacks(options); } + /** + * Removing a Schema from Parse + * @param {Object} options A Backbone-style options object. + * Valid options are:
    + *
  • success: A Backbone-style success callback + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * + * @return {Parse.Promise} A promise that is resolved with the result when + * the query completes. + */ delete(options: FullOptions) { this.assertClassName(); @@ -119,12 +202,22 @@ export default class ParseSchema { })._thenRunCallbacks(options); } + /** + * Assert if ClassName has been filled + * @private + */ assertClassName() { if (!this.className) { throw new Error('You must set a Class Name before making any request.'); } } + /** + * Adding a Field to Create / Update a Schema + * @param {String} name Name of the field will be created on Parse + * @param {String} type TheCan be a (String|Number|Boolean|Date|Parse.File|Parse.GeoPoint|Array|Object|Pointer|Parse.Relation) + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addField(name: string, type: string) { type = type || 'String'; @@ -140,6 +233,12 @@ export default class ParseSchema { return this; } + /** + * Adding an Index to Create / Update a Schema + * @param {String} name Name of the field will be created on Parse + * @param {String} type TheCan be a (String|Number|Boolean|Date|Parse.File|Parse.GeoPoint|Array|Object|Pointer|Parse.Relation) + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addIndex(name: string, index: any) { if (!name) { throw new Error('index name may not be null.'); @@ -153,38 +252,84 @@ export default class ParseSchema { return this; } + /** + * Adding String Field + * @param {String} name Name of the field will be created on Parse + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addString(name: string) { return this.addField(name, 'String'); } + /** + * Adding Number Field + * @param {String} name Name of the field will be created on Parse + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addNumber(name: string) { return this.addField(name, 'Number'); } + /** + * Adding Boolean Field + * @param {String} name Name of the field will be created on Parse + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addBoolean(name: string) { return this.addField(name, 'Boolean'); } + /** + * Adding Date Field + * @param {String} name Name of the field will be created on Parse + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addDate(name: string) { return this.addField(name, 'Date'); } + /** + * Adding File Field + * @param {String} name Name of the field will be created on Parse + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addFile(name: string) { return this.addField(name, 'File'); } + /** + * Adding GeoPoint Field + * @param {String} name Name of the field will be created on Parse + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addGeoPoint(name: string) { return this.addField(name, 'GeoPoint'); } + /** + * Adding Array Field + * @param {String} name Name of the field will be created on Parse + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addArray(name: string) { return this.addField(name, 'Array'); } + /** + * Adding Object Field + * @param {String} name Name of the field will be created on Parse + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addObject(name: string) { return this.addField(name, 'Object'); } + /** + * Adding Pointer Field + * @param {String} name Name of the field will be created on Parse + * @param {String} targetClass Name of the target Pointer Class + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addPointer(name: string, targetClass: string) { if (!name) { throw new Error('field name may not be null.'); @@ -201,6 +346,12 @@ export default class ParseSchema { return this; } + /** + * Adding Relation Field + * @param {String} name Name of the field will be created on Parse + * @param {String} targetClass Name of the target Pointer Class + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ addRelation(name: string, targetClass: string) { if (!name) { throw new Error('field name may not be null.'); @@ -217,10 +368,22 @@ export default class ParseSchema { return this; } + /** + * Deleting a Field to Update on a Schema + * @param {String} name Name of the field will be created on Parse + * @param {String} targetClass Name of the target Pointer Class + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ deleteField(name: string) { this._fields[name] = { __op: 'Delete'}; } + /** + * Deleting an Index to Update on a Schema + * @param {String} name Name of the field will be created on Parse + * @param {String} targetClass Name of the target Pointer Class + * @return {Parse.Schema} Returns the schema, so you can chain this call. + */ deleteIndex(name: string) { this._indexes[name] = { __op: 'Delete'}; } @@ -259,3 +422,5 @@ const DefaultController = { }; CoreManager.setSchemaController(DefaultController); + +export default ParseSchema; From 7c8446c28581e17740d14d49d3d3f00e298cc9c0 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 29 Nov 2017 20:24:54 -0600 Subject: [PATCH 6/7] improve docs --- src/ParseSchema.js | 51 +++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/ParseSchema.js b/src/ParseSchema.js index 4a0300daa..c50b159b2 100644 --- a/src/ParseSchema.js +++ b/src/ParseSchema.js @@ -52,6 +52,7 @@ class ParseSchema { /** * Static method to get all schemas + * * @param {Object} options A Backbone-style options object. * Valid options are:
    *
  • success: A Backbone-style success callback @@ -80,6 +81,7 @@ class ParseSchema { /** * Get the Schema from Parse + * * @param {Object} options A Backbone-style options object. * Valid options are:
      *
    • success: A Backbone-style success callback @@ -110,6 +112,7 @@ class ParseSchema { /** * Create a new Schema on Parse + * * @param {Object} options A Backbone-style options object. * Valid options are:
        *
      • success: A Backbone-style success callback @@ -141,7 +144,8 @@ class ParseSchema { } /** - * Update a Schema from Parse + * Update a Schema on Parse + * * @param {Object} options A Backbone-style options object. * Valid options are:
          *
        • success: A Backbone-style success callback @@ -177,6 +181,7 @@ class ParseSchema { /** * Removing a Schema from Parse + * * @param {Object} options A Backbone-style options object. * Valid options are:
            *
          • success: A Backbone-style success callback @@ -214,7 +219,8 @@ class ParseSchema { /** * Adding a Field to Create / Update a Schema - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @param {String} type TheCan be a (String|Number|Boolean|Date|Parse.File|Parse.GeoPoint|Array|Object|Pointer|Parse.Relation) * @return {Parse.Schema} Returns the schema, so you can chain this call. */ @@ -235,8 +241,9 @@ class ParseSchema { /** * Adding an Index to Create / Update a Schema - * @param {String} name Name of the field will be created on Parse - * @param {String} type TheCan be a (String|Number|Boolean|Date|Parse.File|Parse.GeoPoint|Array|Object|Pointer|Parse.Relation) + * + * @param {String} name Name of the field that will be created on Parse + * @param {String} type Can be a (String|Number|Boolean|Date|Parse.File|Parse.GeoPoint|Array|Object|Pointer|Parse.Relation) * @return {Parse.Schema} Returns the schema, so you can chain this call. */ addIndex(name: string, index: any) { @@ -254,7 +261,8 @@ class ParseSchema { /** * Adding String Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @return {Parse.Schema} Returns the schema, so you can chain this call. */ addString(name: string) { @@ -263,7 +271,8 @@ class ParseSchema { /** * Adding Number Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @return {Parse.Schema} Returns the schema, so you can chain this call. */ addNumber(name: string) { @@ -272,7 +281,8 @@ class ParseSchema { /** * Adding Boolean Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @return {Parse.Schema} Returns the schema, so you can chain this call. */ addBoolean(name: string) { @@ -281,7 +291,8 @@ class ParseSchema { /** * Adding Date Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @return {Parse.Schema} Returns the schema, so you can chain this call. */ addDate(name: string) { @@ -290,7 +301,8 @@ class ParseSchema { /** * Adding File Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @return {Parse.Schema} Returns the schema, so you can chain this call. */ addFile(name: string) { @@ -299,7 +311,8 @@ class ParseSchema { /** * Adding GeoPoint Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @return {Parse.Schema} Returns the schema, so you can chain this call. */ addGeoPoint(name: string) { @@ -308,7 +321,8 @@ class ParseSchema { /** * Adding Array Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @return {Parse.Schema} Returns the schema, so you can chain this call. */ addArray(name: string) { @@ -317,7 +331,8 @@ class ParseSchema { /** * Adding Object Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @return {Parse.Schema} Returns the schema, so you can chain this call. */ addObject(name: string) { @@ -326,7 +341,8 @@ class ParseSchema { /** * Adding Pointer Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @param {String} targetClass Name of the target Pointer Class * @return {Parse.Schema} Returns the schema, so you can chain this call. */ @@ -348,7 +364,8 @@ class ParseSchema { /** * Adding Relation Field - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @param {String} targetClass Name of the target Pointer Class * @return {Parse.Schema} Returns the schema, so you can chain this call. */ @@ -370,7 +387,8 @@ class ParseSchema { /** * Deleting a Field to Update on a Schema - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @param {String} targetClass Name of the target Pointer Class * @return {Parse.Schema} Returns the schema, so you can chain this call. */ @@ -380,7 +398,8 @@ class ParseSchema { /** * Deleting an Index to Update on a Schema - * @param {String} name Name of the field will be created on Parse + * + * @param {String} name Name of the field that will be created on Parse * @param {String} targetClass Name of the target Pointer Class * @return {Parse.Schema} Returns the schema, so you can chain this call. */ From b6f6970c9344b9edd139081e72df6c97b8d2bba5 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 29 Nov 2017 23:41:14 -0600 Subject: [PATCH 7/7] jest tests --- src/ParseSchema.js | 2 +- src/__tests__/CoreManager-test.js | 27 ++ src/__tests__/ParseSchema-test.js | 432 ++++++++++++++++++++++++++++++ 3 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/ParseSchema-test.js diff --git a/src/ParseSchema.js b/src/ParseSchema.js index c50b159b2..4eb8199d0 100644 --- a/src/ParseSchema.js +++ b/src/ParseSchema.js @@ -70,7 +70,7 @@ class ParseSchema { options = options || {}; const controller = CoreManager.getSchemaController(); - return controller.get('', {}, options) + return controller.get('', options) .then((response) => { if (response.results.length === 0) { throw new Error('Schema not found.'); diff --git a/src/__tests__/CoreManager-test.js b/src/__tests__/CoreManager-test.js index 5429907e6..88be5cbd1 100644 --- a/src/__tests__/CoreManager-test.js +++ b/src/__tests__/CoreManager-test.js @@ -315,4 +315,31 @@ describe('CoreManager', () => { CoreManager.setStorageController(controller); expect(CoreManager.getStorageController()).toBe(controller); }); + + it('requires SchemaController to implement certain functionality', () => { + expect(CoreManager.setSchemaController.bind(null, {})).toThrow( + 'SchemaController must implement get()' + ); + + expect(CoreManager.setSchemaController.bind(null, { + send: function() {}, + get: function() {}, + create: function() {}, + update: function() {}, + delete: function() {} + })).not.toThrow(); + }); + + it('can set and get SchemaController', () => { + var controller = { + send: function() {}, + get: function() {}, + create: function() {}, + update: function() {}, + delete: function() {} + }; + + CoreManager.setSchemaController(controller); + expect(CoreManager.getSchemaController()).toBe(controller); + }); }); diff --git a/src/__tests__/ParseSchema-test.js b/src/__tests__/ParseSchema-test.js new file mode 100644 index 000000000..c99311caa --- /dev/null +++ b/src/__tests__/ParseSchema-test.js @@ -0,0 +1,432 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +jest.autoMockOff(); + +var ParseSchema = require('../ParseSchema').default; +var ParsePromise = require('../ParsePromise').default; +var CoreManager = require('../CoreManager'); + +function generateSaveMock(prefix) { + return function(name, payload) { + return ParsePromise.as({ + name: name, + url: prefix + name + }); + }; +} + +var defaultController = CoreManager.getSchemaController(); + +describe('ParseSchema', () => { + it('can create schema', (done) => { + var schema = new ParseSchema('SchemaTest'); + expect(schema.className, 'SchemaTest'); + done(); + }); + + it('can create schema with User Class', (done) => { + var schema = new ParseSchema('User'); + expect(schema.className, '_User'); + done(); + }); + + it('cannot use schema without class', (done) => { + try { + var schema = new ParseSchema(); + schema.assertClassName(); + } catch (e) { + done(); + } + }); + + it('can create schema fields', (done) => { + var schema = new ParseSchema('SchemaTest'); + schema + .addField('defaultFieldString') + .addString('stringField') + .addNumber('numberField') + .addBoolean('booleanField') + .addDate('dateField') + .addFile('fileField') + .addGeoPoint('geoPointField') + .addArray('arrayField') + .addObject('objectField') + .addPointer('pointerField', '_User') + .addRelation('relationField', '_User'); + + expect(schema._fields.defaultFieldString.type, 'String'); + expect(schema._fields.stringField.type, 'String'); + expect(schema._fields.numberField.type, 'Number'); + expect(schema._fields.booleanField.type, 'Boolean'); + expect(schema._fields.dateField.type, 'Date'); + expect(schema._fields.fileField.type, 'File'); + expect(schema._fields.geoPointField.type, 'GeoPoint'); + expect(schema._fields.arrayField.type, 'Array'); + expect(schema._fields.objectField.type, 'Object'); + expect(schema._fields.pointerField.type, 'Pointer'); + expect(schema._fields.relationField.type, 'Relation'); + expect(schema._fields.pointerField.targetClass, '_User'); + expect(schema._fields.relationField.targetClass, '_User'); + done(); + }); + + it('can create schema indexes', (done) => { + var schema = new ParseSchema('SchemaTest'); + schema.addIndex('testIndex', { name: 1 }); + + expect(schema._indexes.name, 1); + done(); + }); + + it('cannot add field with null name', (done) => { + try { + var schema = new ParseSchema('SchemaTest'); + schema.addField(null, 'string'); + } catch (e) { + done(); + } + }); + + it('cannot add field with invalid type', (done) => { + try { + var schema = new ParseSchema('SchemaTest'); + schema.addField('testField', 'unknown'); + } catch (e) { + done(); + } + }); + + it('cannot add index with null name', (done) => { + try { + var schema = new ParseSchema('SchemaTest'); + schema.addIndex(null, {'name': 1}); + } catch (e) { + done(); + } + }); + + it('cannot add index with null index', (done) => { + try { + var schema = new ParseSchema('SchemaTest'); + schema.addIndex('testIndex', null); + } catch (e) { + done(); + } + }); + + it('cannot add pointer with null name', (done) => { + try { + var schema = new ParseSchema('SchemaTest'); + schema.addPointer(null, 'targetClass'); + } catch (e) { + done(); + } + }); + + it('cannot add pointer with null targetClass', (done) => { + try { + var schema = new ParseSchema('SchemaTest'); + schema.addPointer('pointerField', null); + } catch (e) { + done(); + } + }); + + it('cannot add relation with null name', (done) => { + try { + var schema = new ParseSchema('SchemaTest'); + schema.addRelation(null, 'targetClass'); + } catch (e) { + done(); + } + }); + + it('cannot add relation with null targetClass', (done) => { + try { + var schema = new ParseSchema('SchemaTest'); + schema.addRelation('relationField', null); + } catch (e) { + done(); + } + }); + + it('can delete schema field', (done) => { + var schema = new ParseSchema('SchemaTest'); + schema.deleteField('testField'); + expect(schema._fields.testField._op, 'Delete'); + done(); + }); + + it('can delete schema index', (done) => { + var schema = new ParseSchema('SchemaTest'); + schema.deleteIndex('testIndex'); + expect(schema._indexes.testIndex._op, 'Delete'); + done(); + }); + + // CoreManager.setSchemaController({ + // send() {}, + // get() {}, + // create() {}, + // update() {}, + // delete() {}, + // }); + it('can save schema', (done) => { + CoreManager.setSchemaController({ + send() {}, + get() {}, + update() {}, + delete() {}, + create(className, params, options) { + expect(className).toBe('SchemaTest'); + expect(params).toEqual({ + className: 'SchemaTest', + fields: { name: { type: 'String'} }, + indexes: { testIndex: { name: 1 } } + }); + expect(options).toEqual({}); + return ParsePromise.as([]); + }, + }); + + var schema = new ParseSchema('SchemaTest'); + schema.addField('name'); + schema.addIndex('testIndex', {'name': 1}); + schema.save().then((results) => { + expect(results).toEqual([]); + done(); + }); + }); + + it('can update schema', (done) => { + CoreManager.setSchemaController({ + send() {}, + get() {}, + create() {}, + delete() {}, + update(className, params, options) { + expect(className).toBe('SchemaTest'); + expect(params).toEqual({ + className: 'SchemaTest', + fields: { name: { type: 'String'} }, + indexes: { testIndex: { name: 1 } } + }); + expect(options).toEqual({}); + return ParsePromise.as([]); + }, + }); + + var schema = new ParseSchema('SchemaTest'); + schema.addField('name'); + schema.addIndex('testIndex', {'name': 1}); + schema.update().then((results) => { + expect(results).toEqual([]); + done(); + }); + }); + + it('can delete schema', (done) => { + CoreManager.setSchemaController({ + send() {}, + create() {}, + update() {}, + get() {}, + delete(className, options) { + expect(className).toBe('SchemaTest'); + expect(options).toEqual({}); + return ParsePromise.as([]); + }, + }); + + var schema = new ParseSchema('SchemaTest'); + schema.delete().then((results) => { + expect(results).toEqual([]); + done(); + }); + }); + + it('can get schema', (done) => { + CoreManager.setSchemaController({ + send() {}, + create() {}, + update() {}, + delete() {}, + get(className, options) { + expect(className).toBe('SchemaTest'); + expect(options).toEqual({}); + return ParsePromise.as([]); + }, + }); + + var schema = new ParseSchema('SchemaTest'); + schema.get().then((results) => { + expect(results).toEqual([]); + done(); + }); + }); + + it('can get schema with options', (done) => { + CoreManager.setSchemaController({ + send() {}, + create() {}, + update() {}, + delete() {}, + get(className, options) { + expect(className).toBe('SchemaTest'); + expect(options).toEqual({ sessionToken: 1234 }); + return ParsePromise.as([]); + }, + }); + + var schema = new ParseSchema('SchemaTest'); + schema.get({ sessionToken: 1234 }).then((results) => { + expect(results).toEqual([]); + done(); + }); + }); + + it('cannot get empty schema', (done) => { + CoreManager.setSchemaController({ + send() {}, + create() {}, + update() {}, + delete() {}, + get(className, options) { + expect(className).toBe('SchemaTest'); + expect(options).toEqual({}); + return ParsePromise.as(null); + }, + }); + + var schema = new ParseSchema('SchemaTest'); + schema.get().then(() => { + // Should never reach + expect(true).toBe(false); + done(); + }, (error) => { + expect(error.message).toBe('Schema not found.'); + done(); + }); + }); + + it('can get all schema', (done) => { + CoreManager.setSchemaController({ + send() {}, + create() {}, + update() {}, + delete() {}, + get(className, options) { + expect(className).toBe(''); + expect(options).toEqual({}); + return ParsePromise.as({ + results: ['all'] + }); + }, + }); + + ParseSchema.all().then((results) => { + expect(results[0]).toEqual('all'); + done(); + }); + }); + + it('can get all schema with options', (done) => { + CoreManager.setSchemaController({ + send() {}, + create() {}, + update() {}, + delete() {}, + get(className, options) { + expect(className).toBe(''); + expect(options).toEqual({ sessionToken: 1234 }); + return ParsePromise.as({ + results: ['all'] + }); + }, + }); + + ParseSchema.all({ sessionToken: 1234 }).then((results) => { + expect(results[0]).toEqual('all'); + done(); + }); + }); + + it('cannot get all schema when empty', (done) => { + CoreManager.setSchemaController({ + send() {}, + create() {}, + update() {}, + delete() {}, + get(className, options) { + expect(className).toBe(''); + expect(options).toEqual({}); + return ParsePromise.as({ + results: [] + }); + }, + }); + + ParseSchema.all().then(() => { + // Should never reach + expect(true).toBe(false); + done(); + }, (error) => { + expect(error.message).toBe('Schema not found.'); + done(); + }); + }); +}); + +describe('SchemaController', () => { + beforeEach(() => { + CoreManager.setSchemaController(defaultController); + var request = function(method, path, data, options) { + var name = path.substr(path.indexOf('/') + 1); + return ParsePromise.as([]); + }; + var ajax = function(method, path, data, headers) { + var name = path.substr(path.indexOf('/') + 1); + return ParsePromise.as([]); + }; + CoreManager.setRESTController({ request: request, ajax: ajax }); + }); + + it('save schema with sessionToken', (done) => { + var schema = new ParseSchema('SchemaTest'); + schema.save({ sessionToken: 1234 }).then((results) => { + expect(results).toEqual([]); + done(); + }); + }); + + it('get schema', (done) => { + var schema = new ParseSchema('SchemaTest'); + schema.get().then((results) => { + expect(results).toEqual([]); + done(); + }); + }); + + it('update schema', (done) => { + var schema = new ParseSchema('SchemaTest'); + schema.update().then((results) => { + expect(results).toEqual([]); + done(); + }); + }); + + it('delete schema', (done) => { + var schema = new ParseSchema('SchemaTest'); + schema.delete().then((results) => { + expect(results).toEqual([]); + done(); + }); + }); +});