diff --git a/.gitignore b/.gitignore index f37ee6b33..6255a2894 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ coverage dist lib +logs node_modules test_output *~ diff --git a/.travis.yml b/.travis.yml index 38217f6d7..d803bc0ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,25 @@ --- language: node_js node_js: -- 'iojs' +- '5' branches: only: - master +env: + - CXX=g++-4.8 + +services: mongodb + +addons: + apt: + sources: + - mongodb-3.0-precise + - ubuntu-toolchain-r-test + packages: + - mongodb-org-server + - g++-4.8 + +script: npm test && ./run_integration.sh after_script: cat ./coverage/coverage-final.json | ./node_modules/codecov.io/bin/codecov.io.js && rm -rf ./coverage diff --git a/integration/README.md b/integration/README.md new file mode 100644 index 000000000..0c6916407 --- /dev/null +++ b/integration/README.md @@ -0,0 +1,3 @@ +## Integration tests + +These tests run against a local instance of `parse-server` \ No newline at end of file diff --git a/integration/package.json b/integration/package.json new file mode 100644 index 000000000..511748d92 --- /dev/null +++ b/integration/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "dependencies": { + "express": "^4.13.4", + "mocha": "^2.4.5", + "parse-server": "^2.1.1" + }, + "scripts": { + "test": "mocha --reporter dot -t 5000" + } +} diff --git a/integration/server.js b/integration/server.js new file mode 100644 index 000000000..a6353b11a --- /dev/null +++ b/integration/server.js @@ -0,0 +1,31 @@ +var express = require('express'); +var ParseServer = require('parse-server').ParseServer; +var app = express(); + +// Specify the connection string for your mongodb database +// and the location to your Parse cloud code +var api = new ParseServer({ + databaseURI: 'mongodb://localhost:27017/integration', + appId: 'integration', + masterKey: 'notsosecret', + serverURL: 'http://localhost:1337/parse' // Don't forget to change to https if needed +}); + +// Serve the Parse API on the /parse URL prefix +app.use('/parse', api); + +const DatabaseAdapter = require('parse-server/lib/DatabaseAdapter'); + +app.get('/clear', (req, res) => { + var promises = []; + for (var conn in DatabaseAdapter.dbConnections) { + promises.push(DatabaseAdapter.dbConnections[conn].deleteEverything()); + } + Promise.all(promises).then(() => { + res.send('{}'); + }); +}); + +app.listen(1337, () => { + console.log('parse-server running on port 1337.'); +}); diff --git a/integration/test/ArrayOperationsTest.js b/integration/test/ArrayOperationsTest.js new file mode 100644 index 000000000..974d10f76 --- /dev/null +++ b/integration/test/ArrayOperationsTest.js @@ -0,0 +1,380 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +describe('Array Operations', () => { + before((done) => { + Parse.initialize('integration'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear().then(() => { + done(); + }); + }); + + it('adds values', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo']); + object.save().then(() => { + object.add('strings', 'foo'); + object.add('strings', 'bar'); + object.add('strings', 'baz'); + return object.save(); + }).then(() => { + let query = new Parse.Query('TestObject'); + return query.get(object.id); + }).then((o) => { + let strings = o.get('strings'); + assert.equal(strings.length, 4); + assert.equal(strings[0], 'foo'); + assert.equal(strings[1], 'foo'); + assert.equal(strings[2], 'bar'); + assert.equal(strings[3], 'baz'); + done(); + }); + }); + + it('adds values on a fresh object', (done) => { + let object = new Parse.Object('TestObject'); + object.add('strings', 'foo'); + object.add('strings', 'bar'); + object.add('strings', 'baz'); + object.save().then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + assert.equal(strings.length, 3); + assert.equal(strings[0], 'foo'); + assert.equal(strings[1], 'bar'); + assert.equal(strings[2], 'baz'); + done(); + }); + }); + + it('sets then adds objects', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo']); + object.save().then(() => { + object.set('strings', ['bar']); + object.add('strings', 'bar'); + object.add('strings', 'baz'); + return object.save(); + }).then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + assert.equal(strings.length, 3); + assert.equal(strings[0], 'bar'); + assert.equal(strings[1], 'bar'); + assert.equal(strings[2], 'baz'); + done(); + }); + }); + + it('adds values atomically', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo']); + object.save().then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + assert(o !== object); + o.add('strings', 'bar'); + o.add('strings', 'baz'); + object.add('strings', 'bar'); + object.add('strings', 'baz'); + return o.save(); + }).then(() => { + assert(object.dirty()); + return object.save(); + }).then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + assert.equal(strings.length, 5); + done(); + }); + }); + + it('adds unique values', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo']); + object.save().then(() => { + object.addUnique('strings', 'foo'); + object.addUnique('strings', 'bar'); + object.addUnique('strings', 'foo'); + object.addUnique('strings', 'baz'); + return object.save(); + }).then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + strings.sort(); + assert.equal(strings.length, 3); + assert.equal(strings[0], 'bar'); + assert.equal(strings[1], 'baz'); + assert.equal(strings[2], 'foo'); + done(); + }); + }); + + it('adds unique values on a fresh object', (done) => { + let object = new Parse.Object('TestObject'); + object.addUnique('strings', 'foo'); + object.addUnique('strings', 'bar'); + object.addUnique('strings', 'foo'); + object.addUnique('strings', 'baz'); + object.save().then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + strings.sort(); + assert.equal(strings.length, 3); + assert.equal(strings[0], 'bar'); + assert.equal(strings[1], 'baz'); + assert.equal(strings[2], 'foo'); + done(); + }); + }); + + it('adds unique values after a set', () => { + let object = new Parse.Object('TestObject'); + object.set('numbers', [1,2,3,3,4,4]); + [1,4,4,5].forEach((number) => { + object.addUnique('numbers', number); + }); + assert.equal(object.get('numbers').length, 7); + }); + + it('adds unique values atomically', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo']); + object.save().then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + o.addUnique('strings', 'bar'); + o.addUnique('strings', 'baz'); + object.addUnique('strings', 'baz'); + object.addUnique('strings', 'bat'); + return o.save(); + }).then(() => { + assert(object.dirty()); + return object.save(); + }).then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + strings.sort(); + assert.equal(strings.length, 4); + assert.equal(strings[0], 'bar'); + assert.equal(strings[1], 'bat'); + assert.equal(strings[2], 'baz'); + assert.equal(strings[3], 'foo'); + done(); + }); + }); + + it('removes values', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo', 'foo', 'bar', 'baz']); + object.save().then(() => { + object.remove('strings', 'foo'); + object.remove('strings', 'baz'); + object.remove('strings', 'bat'); + return object.save(); + }).then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + assert.equal(strings.length, 1); + assert.equal(strings[0], 'bar'); + done(); + }); + }); + + it('sets then removes values', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo']); + object.save().then(() => { + object.set('strings', ['bar', 'baz', 'bat']); + object.remove('strings', 'bar'); + object.remove('strings', 'baz'); + object.remove('strings', 'zzz'); + return object.save(); + }).then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + assert.equal(strings.length, 1); + assert.equal(strings[0], 'bat'); + done(); + }); + }); + + it('removes values on a fresh object', (done) => { + let object = new Parse.Object('TestObject'); + object.remove('strings', 'foo'); + object.remove('strings', 'baz'); + object.remove('strings', 'bat'); + object.save().then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + assert.equal(strings.length, 0); + done(); + }); + }); + + it('removes values atomically', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo', 'foo', 'bar', 'baz']); + object.save().then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + o.remove('strings', 'foo'); + o.remove('strings', 'zzz'); + object.remove('strings', 'bar'); + object.remove('strings', 'zzz'); + return o.save(); + }).then(() => { + assert(object.dirty()); + return object.save(); + }).then(() => { + return new Parse.Query('TestObject').get(object.id); + }).then((o) => { + let strings = o.get('strings'); + strings.sort(); + assert.equal(strings.length, 1); + assert.equal(strings[0], 'baz'); + done(); + }); + }); + + it('fails when combining add unique with add', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo', 'bar']); + object.save().then(() => { + object.add('strings', 'bar'); + object.addUnique('strings', 'bar'); + }).fail((e) => { + assert.equal(e.message, 'Cannot merge AddUnique Op with the previous Op'); + done(); + }); + }); + + it('fails when combining add with add unique', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo', 'bar']); + object.save().then(() => { + object.addUnique('strings', 'bar'); + object.add('strings', 'bar'); + }).fail((e) => { + assert.equal(e.message, 'Cannot merge Add Op with the previous Op'); + done(); + }); + }); + + it('fails when combining remove with add', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo', 'bar']); + object.save().then(() => { + object.add('strings', 'bar'); + object.remove('strings', 'bar'); + }).fail((e) => { + assert.equal(e.message, 'Cannot merge Remove Op with the previous Op'); + done(); + }); + }); + + it('fails when combining add with remove', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo', 'bar']); + object.save().then(() => { + object.remove('strings', 'bar'); + object.add('strings', 'bar'); + }).fail((e) => { + assert.equal(e.message, 'Cannot merge Add Op with the previous Op'); + done(); + }); + }); + + it('fails when combining remove with add unique', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo', 'bar']); + object.save().then(() => { + object.addUnique('strings', 'bar'); + object.remove('strings', 'bar'); + }).fail((e) => { + assert.equal(e.message, 'Cannot merge Remove Op with the previous Op'); + done(); + }); + }); + + it('fails when combining remove with add unique', (done) => { + let object = new Parse.Object('TestObject'); + object.set('strings', ['foo', 'bar']); + object.save().then(() => { + object.remove('strings', 'bar'); + object.addUnique('strings', 'bar'); + }).fail((e) => { + assert.equal(e.message, 'Cannot merge AddUnique Op with the previous Op'); + done(); + }); + }); + + it('adds unique objects by id', (done) => { + let snowflake = new Parse.Object('Snowflake'); + let pocket = new Parse.Object('Pocket'); + snowflake.set('color', 'white'); + snowflake.save().then(() => { + pocket.set('snowflakes', [snowflake]); + let snowflakeQuery = new Parse.Query('Snowflake'); + return snowflakeQuery.get(snowflake.id); + }).then((flake) => { + pocket.addUnique('snowflakes', flake); + assert.equal(pocket.get('snowflakes').length, 1); + return pocket.save(); + }).then(() => { + let pocketQuery = new Parse.Query('Pocket'); + pocketQuery.include('snowflakes'); + return pocketQuery.get(pocket.id); + }).then((newPocket) => { + assert.notEqual(pocket, newPocket); + assert.equal(newPocket.get('snowflakes').length, 1); + let flake = newPocket.get('snowflakes')[0]; + assert.equal(flake.get('color'), 'white'); + done(); + }); + }); + + it('removes objects by id', (done) => { + let badEgg = new Parse.Object('Egg'); + badEgg.set('quality', 'rotten'); + let goodEgg = new Parse.Object('Egg'); + goodEgg.set('quality', 'good'); + let ostrichEgg = new Parse.Object('Egg'); + ostrichEgg.set('quality', 'huge'); + let eggs = [badEgg, goodEgg, ostrichEgg]; + let shelf = new Parse.Object('Shelf'); + Parse.Object.saveAll(eggs).then(() => { + shelf.set('eggs', eggs); + let badEggQuery = new Parse.Query('Egg'); + return badEggQuery.get(badEgg.id); + }).then((badEggRef) => { + assert.notEqual(badEgg, badEggRef); + shelf.remove('eggs', badEggRef); + let fetchedEggs = shelf.get('eggs'); + assert.equal(fetchedEggs.length, 2); + assert.equal(fetchedEggs[0].get('quality'), 'good'); + assert.equal(fetchedEggs[1].get('quality'), 'huge'); + return shelf.save(); + }).then(() => { + return shelf.fetch(); + }).then(() => { + assert.equal(shelf.get('eggs').length, 2); + done(); + }); + }); +}); diff --git a/integration/test/DirtyTest.js b/integration/test/DirtyTest.js new file mode 100644 index 000000000..7b05d98a2 --- /dev/null +++ b/integration/test/DirtyTest.js @@ -0,0 +1,209 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); +const Parent = Parse.Object.extend('Parent'); +const Child = Parse.Object.extend('Child'); + +describe('Dirty Objects', () => { + before((done) => { + Parse.initialize('integration'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear().then(() => { + done(); + }); + }); + + it('tracks dirty arrays', (done) => { + let array = [1]; + let object = new TestObject(); + object.set('scores', array); + assert.equal(object.get('scores').length, 1); + object.save().then(() => { + array.push(2); + assert.equal(object.get('scores').length, 2); + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('scores').length, 2); + done(); + }); + }); + + it('tracks dirty arrays after fetch', (done) => { + let object = new TestObject(); + object.set('scores', [1]); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + let array = o.get('scores'); + array.push(2); + return o.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('scores').length, 2); + done(); + }); + }); + + it('tracks dirty objects', (done) => { + let dict = {player1: 1}; + let object = new TestObject(); + object.set('scoreMap', dict); + object.save().then(() => { + dict.player2 = 2; + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(Object.keys(o.get('scoreMap')).length, 2); + done(); + }); + }); + + it('tracks dirty objects after fetch', (done) => { + let dict = {player1: 1}; + let object = new TestObject(); + object.set('scoreMap', dict); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + let dictAgain = o.get('scoreMap'); + dictAgain.player2 = 2; + return o.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(Object.keys(o.get('scoreMap')).length, 2); + done(); + }); + }); + + it('tracks dirty geo points', (done) => { + let geo = new Parse.GeoPoint(5, 5); + let object = new TestObject(); + object.set('location', geo); + object.save().then(() => { + geo.latitude = 10; + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('location').latitude, 10); + done(); + }); + }); + + it('tracks dirty geo points on fresh objects', (done) => { + let geo = new Parse.GeoPoint(1.0, 1.0); + let object = new TestObject(); + object.set('location', geo); + geo.latitude = 2.0; + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('location').latitude, 2); + done(); + }); + }); + + it('does not resave relations with dirty children', () => { + let parent = new Parent(); + let child = new Child(); + let newChild; + let parentAgain; + child.set('ghostbuster', 'peter'); + parent.set('child', child); + parent.save().then(() => { + assert.equal(parent.get('child'), child); + assert.equal(child.get('ghostbuster'), 'peter'); + + let query = new Parse.Query(Parent); + return query.get(parent.id); + }).then((again) => { + parentAgain = again; + newChild = new Child(); + newChild.set('ghostbuster', 'ray'); + parentAgain.set('child', newChild); + return parentAgain.save(); + }).then(() => { + assert.equal(parent.get('child'), child); + assert.equal(parentAgain.get('child'), newChild); + assert.equal(child.get('ghostbuster'), 'peter'); + assert.equal(newChild.get('ghostbuster'), 'ray'); + + // Now parent's child is newChild. If we change the original + // child, it shouldn't affect parent. + child.set('ghostbuster', 'egon'); + assert.equal(parent.get('child').get('ghostbuster'), 'egon'); + + return parent.save(); + }).then(()=> { + assert.equal(parent.get('child'), child); + assert.equal(parentAgain.get('child'), newChild); + assert.equal(child.get('ghostbuster'), 'egon'); + assert.equal(newChild.get('ghostbuster'), 'ray'); + + let query = new Parse.Query(Parent); + return query.get(parent.id); + }).then((yetAgain) => { + assert.equal(parent.get('child'), child); + assert.equal(parentAgain.get('child'), newChild); + assert.equal(yetAgain.get('child').id, newChild.id); + assert.equal(child.get('ghostbuster'), 'egon'); + assert.equal(newChild.get('ghostbuster'), 'ray'); + + let newChildAgain = yetAgain.get('child'); + assert.equal(newChildAgain.id, newChild.id); + return newChildAgain.fetch(); + }).then((c) => { + assert.equal(c.get('ghostbuster'), 'ray'); + done(); + }); + }); + + it('does not dirty two-way pointers on saveAll', (done) => { + let parent = new Parent(); + let child = new Child(); + + child.save().then(() => { + child.set('property', 'x'); + parent.set('children', [child]); + child.set('parent', parent); + return Parse.Object.saveAll([parent, child]); + }).then((results) => { + assert.equal(results[0].dirty(), false); + assert.equal(results[1].dirty(), false); + done(); + }); + }); + + it('unset fields should not stay dirty', (done) => { + let object = new TestObject(); + object.save({ foo: 'bar' }).then(() => { + assert.equal(object.dirty(), false); + object.unset('foo'); + assert.equal(object.dirty(), true); + + return object.save(); + }).then(() => { + assert.equal(object.dirty(), false); + done(); + }); + }); +}); diff --git a/integration/test/IncrementTest.js b/integration/test/IncrementTest.js new file mode 100644 index 000000000..7a385d86b --- /dev/null +++ b/integration/test/IncrementTest.js @@ -0,0 +1,228 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); + +describe('Increment', () => { + before((done) => { + Parse.initialize('integration'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear().then(() => { + done(); + }); + }); + + it('can increment a field', (done) => { + let object = new TestObject(); + object.set('score', 1); + object.save().then(() => { + object.increment('score'); + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('score'), 2); + done(); + }); + }); + + it('can increment on a fresh object', () => { + let object = new TestObject(); + object.set('score', 1); + object.increment('score'); + assert.equal(object.get('score'), 2); + }); + + it('can increment by a value', (done) => { + let object = new TestObject(); + object.set('score', 1); + object.save().then(() => { + object.increment('score', 10); + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('score'), 11); + done(); + }); + }); + + it('can increment with negative numbers', (done) => { + let object = new TestObject(); + object.set('score', 1); + object.save().then(() => { + object.increment('score', -1); + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('score'), 0); + done(); + }); + }); + + it('can increment with floats', (done) => { + let object = new TestObject(); + object.set('score', 1.0); + object.save().then(() => { + object.increment('score', 1.5); + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('score'), 2.5); + done(); + }); + }); + + it('increments atomically', (done) => { + let object = new TestObject(); + object.set('score', 1); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + object.increment('score'); + o.increment('score'); + return o.save(); + }).then(() => { + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('score'), 3); + done(); + }); + }); + + it('gets a new value back on increment', (done) => { + let object = new TestObject(); + let objectAgain; + object.set('score', 1); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + objectAgain = o; + assert.equal(o.get('score'), 1); + object.increment('score'); + assert.equal(object.get('score'), 2); + return object.save(); + }).then(() => { + assert.equal(object.get('score'), 2); + objectAgain.increment('score'); + return objectAgain.save(); + }).then(() => { + assert.equal(objectAgain.get('score'), 3); + done(); + }); + }); + + it('can combine increment with other updates', (done) => { + let object = new TestObject(); + object.set('score', 1); + object.set('name', 'hungry'); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + o.increment('score'); + o.set('name', 'parse'); + return o.save(); + }).then(() => { + object.increment('score'); + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('name'), 'parse'); + assert.equal(o.get('score'), 3); + done(); + }); + }); + + it('does not increment non-numbers', (done) => { + let object = new TestObject(); + object.set('not_score', 'foo'); + object.save().then(() => { + try { + object.increment('not_score'); + } catch (e) { + done(); + } + }); + }); + + it('can increment on a deleted field', (done) => { + let object = new TestObject(); + object.set('score', 1); + object.save().then(() => { + object.unset('score'); + object.increment('score'); + assert.equal(object.get('score'), 1); + return object.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('score'), 1); + done(); + }); + }); + + it('can increment with an empty field on a fresh object', (done) => { + let object = new TestObject(); + object.increment('score'); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + o.get('score', 1); + done(); + }); + }); + + it('can increment with an empty field', () => { + let object = new TestObject(); + let objectAgain; + object.save().then(() => { + object.increment('score'); + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + objectAgain = o; + o.increment('score'); + return object.save(); + }).then(() => { + return objectAgain.save(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('score'), 2); + done(); + }); + }); + + it('solidifies the type by incrementing', (done) => { + let object = new TestObject(); + object.increment('numeric'); + object.save().then(() => { + object.set('numeric', 'x'); + return object.save(); + }).fail(() => { + done(); + }); + }); +}); diff --git a/integration/test/ParseACLTest.js b/integration/test/ParseACLTest.js new file mode 100644 index 000000000..0bd5b6f15 --- /dev/null +++ b/integration/test/ParseACLTest.js @@ -0,0 +1,616 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); + +describe('Parse.ACL', () => { + before((done) => { + Parse.initialize('integration'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + Parse.User.enableUnsafeCurrentUser(); + clear().then(() => { + done(); + }); + }); + + it('acl must be valid', () => { + let user = new Parse.User(); + assert.equal(user.setACL(`Ceci n'est pas un ACL.`), false); + }); + + it('can refresh object with acl', (done) => { + let user = new Parse.User(); + let object = new TestObject(); + user.set('username', 'alice'); + user.set('password', 'wonderland'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then((x) => { + return object.fetch(); + }).then((o) => { + assert(o); + done(); + }); + }); + + it('disables public get access', (done) => { + let user = new Parse.User(); + let object = new TestObject(); + user.set('username', 'getter'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + assert.equal(object.getACL().getReadAccess(user), true); + assert.equal(object.getACL().getWriteAccess(user), true); + assert.equal(object.getACL().getPublicReadAccess(), false); + assert.equal(object.getACL().getPublicWriteAccess(), false); + + Parse.User.logOut(); + + return new Parse.Query(TestObject).get(object.id); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('disables public find access', (done) => { + let user = new Parse.User(); + let object = new Parse.Object('UniqueObject'); + user.set('username', 'finder'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + assert.equal(object.getACL().getReadAccess(user), true); + assert.equal(object.getACL().getWriteAccess(user), true); + assert.equal(object.getACL().getPublicReadAccess(), false); + assert.equal(object.getACL().getPublicWriteAccess(), false); + + Parse.User.logOut(); + + return new Parse.Query('UniqueObject').find(); + }).then((o) => { + assert.equal(o.length, 0); + done(); + }); + }); + + it('disables public update access', (done) => { + let user = new Parse.User(); + let object = new Parse.Object('UniqueObject'); + user.set('username', 'updater'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + assert.equal(object.getACL().getReadAccess(user), true); + assert.equal(object.getACL().getWriteAccess(user), true); + assert.equal(object.getACL().getPublicReadAccess(), false); + assert.equal(object.getACL().getPublicWriteAccess(), false); + + Parse.User.logOut(); + + object.set('score', 10); + return object.save(); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('disables public delete access', (done) => { + let user = new Parse.User(); + let object = new Parse.Object(TestObject); + user.set('username', 'deleter'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + assert.equal(object.getACL().getReadAccess(user), true); + assert.equal(object.getACL().getWriteAccess(user), true); + assert.equal(object.getACL().getPublicReadAccess(), false); + assert.equal(object.getACL().getPublicWriteAccess(), false); + + Parse.User.logOut(); + + return object.destroy(); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('allows logged in get', (done) => { + let user = new Parse.User(); + let object = new TestObject(); + user.set('username', 'getter2'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + return new Parse.Query(TestObject).get(object.id); + }).then((o) => { + assert(o); + done(); + }); + }); + + it('allows logged in find', (done) => { + let user = new Parse.User(); + let object = new Parse.Object('UniqueObject'); + user.set('username', 'finder2'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + return new Parse.Query('UniqueObject').find(); + }).then((o) => { + assert(o.length > 0); + done(); + }); + }); + + it('allows logged in update', (done) => { + let user = new Parse.User(); + let object = new Parse.Object('UniqueObject'); + user.set('username', 'updater2'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + object.set('score', 10); + return object.save(); + }).then(() => { + assert.equal(object.get('score'), 10); + done(); + }); + }); + + it('allows logged in delete', (done) => { + let user = new Parse.User(); + let object = new Parse.Object(TestObject); + user.set('username', 'deleter2'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + return object.destroy(); + }).then(() => { + done(); + }); + }); + + it('enables get with public read', (done) => { + let user = new Parse.User(); + let object = new TestObject(); + user.set('username', 'getter3'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + object.getACL().setPublicReadAccess(true); + return object.save(); + }).then(() => { + Parse.User.logOut(); + return new Parse.Query(TestObject).get(object.id); + }).then((o) => { + assert(o); + done(); + }); + }); + + it('enables find with public read', (done) => { + let user = new Parse.User(); + let object = new Parse.Object('AlsoUniqueObject'); + user.set('username', 'finder3'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + object.getACL().setPublicReadAccess(true); + return object.save(); + }).then(() => { + Parse.User.logOut(); + return new Parse.Query('AlsoUniqueObject').find(); + }).then((o) => { + assert(o.length > 0); + done(); + }); + }); + + it('does not enable update with public read', (done) => { + let user = new Parse.User(); + let object = new Parse.Object('UniqueObject'); + user.set('username', 'updater3'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + object.getACL().setPublicReadAccess(true); + return object.save(); + }).then(() => { + Parse.User.logOut(); + object.set('score', 10); + return object.save(); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('does not enable delete with public read', (done) => { + let user = new Parse.User(); + let object = new Parse.Object(TestObject); + user.set('username', 'deleter3'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + object.getACL().setPublicReadAccess(true); + return object.save(); + }).then(() => { + Parse.User.logOut(); + return object.destroy(); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('does not enable get with public write', (done) => { + let user = new Parse.User(); + let object = new TestObject(); + user.set('username', 'getter4'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + object.getACL().setPublicWriteAccess(true); + return object.save(); + }).then(() => { + Parse.User.logOut(); + return new Parse.Query(TestObject).get(object.id); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('does not enable find with public write', (done) => { + let user = new Parse.User(); + let object = new Parse.Object('AnotherUniqueObject'); + user.set('username', 'finder4'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + object.getACL().setPublicWriteAccess(true); + return object.save(); + }).then(() => { + Parse.User.logOut(); + return new Parse.Query('AnotherUniqueObject').find(); + }).then((o) => { + assert.equal(o.length, 0); + done(); + }); + }); + + it('enables update with public read', (done) => { + let user = new Parse.User(); + let object = new Parse.Object('UniqueObject'); + user.set('username', 'updater4'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + object.getACL().setPublicWriteAccess(true); + return object.save(); + }).then(() => { + Parse.User.logOut(); + object.set('score', 10); + return object.save(); + }).then(() => { + done(); + }); + }); + + it('enables delete with public read', (done) => { + let user = new Parse.User(); + let object = new TestObject(); + user.set('username', 'deleter4'); + user.set('password', 'secret'); + user.signUp().then(() => { + let acl = new Parse.ACL(user); + object.setACL(acl); + return object.save(); + }).then(() => { + object.getACL().setPublicWriteAccess(true); + return object.save(); + }).then(() => { + Parse.User.logOut(); + return object.destroy(); + }).then(() => { + done(); + }); + }); + + it('can grant get access to another user', (done) => { + let user1, user2; + let object = new TestObject(); + Parse.User.signUp('aaa', 'password').then((u) => { + user1 = u; + Parse.User.logOut(); + + return Parse.User.signUp('bbb', 'password'); + }).then((u) => { + user2 = u; + let acl = new Parse.ACL(user2); + acl.setWriteAccess(user1, true); + acl.setReadAccess(user1, true); + object.setACL(acl); + return object.save(); + }).then(() => { + return Parse.User.logIn('aaa', 'password'); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.id, object.id); + done(); + }); + }); + + it('can grant find access to another user', (done) => { + let user1, user2; + let object = new Parse.Object('ThatOneObject'); + Parse.User.signUp('ccc', 'password').then((u) => { + user1 = u; + Parse.User.logOut(); + + return Parse.User.signUp('ddd', 'password'); + }).then((u) => { + user2 = u; + let acl = new Parse.ACL(user2); + acl.setWriteAccess(user1, true); + acl.setReadAccess(user1, true); + object.setACL(acl); + return object.save(); + }).then(() => { + return Parse.User.logIn('ccc', 'password'); + }).then(() => { + let query = new Parse.Query('ThatOneObject'); + return query.find(); + }).then((o) => { + assert(o.length > 0); + done(); + }); + }); + + it('can grant update access to another user', (done) => { + let user1, user2; + let object = new TestObject(); + Parse.User.signUp('eee', 'password').then((u) => { + user1 = u; + Parse.User.logOut(); + + return Parse.User.signUp('fff', 'password'); + }).then((u) => { + user2 = u; + let acl = new Parse.ACL(user2); + acl.setWriteAccess(user1, true); + acl.setReadAccess(user1, true); + object.setACL(acl); + return object.save(); + }).then(() => { + return Parse.User.logIn('eee', 'password'); + }).then(() => { + object.set('score', 10); + return object.save(); + }).then((o) => { + assert.equal(o.get('score'), 10); + done(); + }); + }); + + it('can grant delete access to another user', (done) => { + let user1, user2; + let object = new TestObject(); + Parse.User.signUp('ggg', 'password').then((u) => { + user1 = u; + Parse.User.logOut(); + + return Parse.User.signUp('hhh', 'password'); + }).then((u) => { + user2 = u; + let acl = new Parse.ACL(user2); + acl.setWriteAccess(user1, true); + acl.setReadAccess(user1, true); + object.setACL(acl); + return object.save(); + }).then(() => { + return Parse.User.logIn('ggg', 'password'); + }).then(() => { + return object.destroy(); + }).then(() => { + done(); + }); + }); + + it('does not grant public get access with another user acl', (done) => { + let user1, user2; + let object = new TestObject(); + Parse.User.signUp('iii', 'password').then((u) => { + user1 = u; + Parse.User.logOut(); + + return Parse.User.signUp('jjj', 'password'); + }).then((u) => { + user2 = u; + let acl = new Parse.ACL(user2); + acl.setWriteAccess(user1, true); + acl.setReadAccess(user1, true); + object.setACL(acl); + return object.save(); + }).then(() => { + return Parse.User.logOut(); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('does not grant public find access with another user acl', (done) => { + let user1, user2; + let object = new Parse.Object('ThatOneObject'); + Parse.User.signUp('kkk', 'password').then((u) => { + user1 = u; + Parse.User.logOut(); + + return Parse.User.signUp('lll', 'password'); + }).then((u) => { + user2 = u; + let acl = new Parse.ACL(user2); + acl.setWriteAccess(user1, true); + acl.setReadAccess(user1, true); + object.setACL(acl); + return object.save(); + }).then(() => { + return Parse.User.logOut(); + }).then(() => { + let query = new Parse.Query('ThatOneObject'); + return query.find(); + }).then((o) => { + assert.equal(o.length, 0); + done(); + }); + }); + + it('does not grant public update access with another user acl', (done) => { + let user1, user2; + let object = new TestObject(); + Parse.User.signUp('mmm', 'password').then((u) => { + user1 = u; + Parse.User.logOut(); + + return Parse.User.signUp('nnn', 'password'); + }).then((u) => { + user2 = u; + let acl = new Parse.ACL(user2); + acl.setWriteAccess(user1, true); + acl.setReadAccess(user1, true); + object.setACL(acl); + return object.save(); + }).then(() => { + return Parse.User.logOut(); + }).then(() => { + object.set('score', 10); + return object.save(); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('does not grant public update access with another user acl', (done) => { + let user1, user2; + let object = new TestObject(); + Parse.User.signUp('ooo', 'password').then((u) => { + user1 = u; + Parse.User.logOut(); + + return Parse.User.signUp('ppp', 'password'); + }).then((u) => { + user2 = u; + let acl = new Parse.ACL(user2); + acl.setWriteAccess(user1, true); + acl.setReadAccess(user1, true); + object.setACL(acl); + return object.save(); + }).then(() => { + return Parse.User.logOut(); + }).then(() => { + return object.destroy(); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('allows access with an empty acl', (done) => { + Parse.User.signUp('tdurden', 'mayhem', { + ACL: new Parse.ACL(), + foo: 'bar' + }).then((user) => { + Parse.User.logOut(); + return Parse.User.logIn('tdurden', 'mayhem'); + }).then((user) => { + assert.equal(user.get('foo'), 'bar'); + done(); + }); + }); + + it('fetches the ACL with included pointers', (done) => { + let obj1 = new Parse.Object('TestClass1'); + let obj2 = new Parse.Object('TestClass2'); + let acl = new Parse.ACL(); + + acl.setPublicReadAccess(true); + obj2.set('ACL', acl); + obj1.set('other', obj2); + obj1.save().then(() => { + let query = new Parse.Query('TestClass1'); + return query.first(); + }).then((obj1again) => { + assert(obj1again); + assert(!obj1again.get('other').get('ACL')); + let query = new Parse.Query('TestClass1'); + query.include('other'); + return query.first(); + }).then((obj1withInclude) => { + assert(obj1withInclude.get('other').get('ACL')); + done(); + }); + }); +}); \ No newline at end of file diff --git a/integration/test/ParseGeoBoxTest.js b/integration/test/ParseGeoBoxTest.js new file mode 100644 index 000000000..78b40d4ca --- /dev/null +++ b/integration/test/ParseGeoBoxTest.js @@ -0,0 +1,119 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +describe('Geo Box', () => { + before(() => { + Parse.initialize('integration'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + }); + + beforeEach((done) => { + clear().then(() => { + done(); + }); + }); + + it('can query geo boxes', () => { + let caltrainStationLocation = new Parse.GeoPoint(37.776346, -122.394218); + let caltrainStation = new Parse.Object('Location'); + caltrainStation.set('location', caltrainStationLocation); + caltrainStation.set('name', 'caltrain'); + + let santaClaraLocation = new Parse.GeoPoint(37.325635, -121.945753); + let santaClara = new Parse.Object('Location'); + santaClara.set('location', santaClaraLocation); + santaClara.set('name', 'santa clara'); + + let southwestOfSF = new Parse.GeoPoint(37.708813, -122.526398); + let northeastOfSF = new Parse.GeoPoint(37.822802, -122.373962); + + Parse.Object.saveAll([caltrainStation, santaClara]).then(() => { + let query = new Parse.Query('Location'); + query.withinGeoBox('location', southwestOfSF, northeastOfSF); + return query.find(); + }).then((objectsInSF) => { + assert.equal(objectsInSF, 1); + assert.equal(objectsInSF[0].get('name'), 'caltrain'); + done(); + }); + }); + + it('can swap geo box corners', () => { + let caltrainStationLocation = new Parse.GeoPoint(37.776346, -122.394218); + let caltrainStation = new Parse.Object('Location'); + caltrainStation.set('location', caltrainStationLocation); + caltrainStation.set('name', 'caltrain'); + + let santaClaraLocation = new Parse.GeoPoint(37.325635, -121.945753); + let santaClara = new Parse.Object('Location'); + santaClara.set('location', santaClaraLocation); + santaClara.set('name', 'santa clara'); + + let southwestOfSF = new Parse.GeoPoint(37.708813, -122.526398); + let northeastOfSF = new Parse.GeoPoint(37.822802, -122.373962); + + Parse.Object.saveAll([caltrainStation, santaClara]).then(() => { + let query = new Parse.Query('Location'); + query.withinGeoBox('location', northeastOfSF, southwestOfSF); + return query.find(); + }).fail(() => { + // Query should fail for crossing the date line + done(); + }); + }); + + it('can swap longitude', () => { + let caltrainStationLocation = new Parse.GeoPoint(37.776346, -122.394218); + let caltrainStation = new Parse.Object('Location'); + caltrainStation.set('location', caltrainStationLocation); + caltrainStation.set('name', 'caltrain'); + + let santaClaraLocation = new Parse.GeoPoint(37.325635, -121.945753); + let santaClara = new Parse.Object('Location'); + santaClara.set('location', santaClaraLocation); + santaClara.set('name', 'santa clara'); + + let northwestOfSF = new Parse.GeoPoint(37.822802, -122.526398); + let southeastOfSF = new Parse.GeoPoint(37.708813, -122.373962); + + Parse.Object.saveAll([caltrainStation, santaClara]).then(() => { + let query = new Parse.Query('Location'); + query.withinGeoBox('location', southwestOfSF, northeastOfSF); + return query.find(); + }).then((objectsInSF) => { + assert.equal(objectsInSF, 1); + assert.equal(objectsInSF[0].get('name'), 'caltrain'); + done(); + }); + }); + + it('can swap latitude', () => { + let caltrainStationLocation = new Parse.GeoPoint(37.776346, -122.394218); + let caltrainStation = new Parse.Object('Location'); + caltrainStation.set('location', caltrainStationLocation); + caltrainStation.set('name', 'caltrain'); + + let santaClaraLocation = new Parse.GeoPoint(37.325635, -121.945753); + let santaClara = new Parse.Object('Location'); + santaClara.set('location', santaClaraLocation); + santaClara.set('name', 'santa clara'); + + let northwestOfSF = new Parse.GeoPoint(37.822802, -122.526398); + let southeastOfSF = new Parse.GeoPoint(37.708813, -122.373962); + + Parse.Object.saveAll([caltrainStation, santaClara]).then(() => { + let query = new Parse.Query('Location'); + query.withinGeoBox('location', southwestOfSF, northeastOfSF); + return query.find(); + }).then((objectsInSF) => { + assert.equal(objectsInSF, 1); + assert.equal(objectsInSF[0].get('name'), 'caltrain'); + done(); + }); + }); +}); \ No newline at end of file diff --git a/integration/test/ParseGeoPointTest.js b/integration/test/ParseGeoPointTest.js new file mode 100644 index 000000000..400d39f60 --- /dev/null +++ b/integration/test/ParseGeoPointTest.js @@ -0,0 +1,240 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); +const TestPoint = Parse.Object.extend('TestPoint'); + +describe('Geo Point', () => { + before(() => { + Parse.initialize('integration'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear().then(() => { + let sacramento = new TestPoint(); + sacramento.set('location', new Parse.GeoPoint(38.52, -121.50)); + sacramento.set('name', 'Sacramento'); + + let honolulu = new TestPoint(); + honolulu.set('location', new Parse.GeoPoint(21.35, -157.93)); + honolulu.set('name', 'Honolulu'); + + let sf = new TestPoint(); + sf.set('location', new Parse.GeoPoint(37.75, -122.68)); + sf.set('name', 'San Francisco'); + + return Parse.Object.saveAll([sacramento, honolulu, sf]); + }).then(() => { + done(); + }); + }); + + it('can save geo points', (done) => { + let point = new Parse.GeoPoint(44.0, -11.0); + let obj = new TestObject(); + obj.set('location', point) + obj.set('name', 'Ferndale'); + obj.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert(o.get('location')); + assert.equal(o.get('location').latitude, 44.0); + assert.equal(o.get('location').longitude, -11.0); + done(); + }); + }); + + it('can only store one geo point per object', (done) => { + let point = new Parse.GeoPoint(20, 20); + let obj = new TestObject(); + obj.set('locationOne', point); + obj.set('locationTwo', point); + obj.save().fail((e) => { + assert.equal(e.code, Parse.Error.INCORRECT_TYPE); + done(); + }); + }); + + it('can sequence a line of points by distance', (done) => { + let line = []; + for (let i = 0; i < 10; i++) { + let obj = new TestObject(); + let point = new Parse.GeoPoint(i * 4 - 12, i * 3.2 - 11); + obj.set('location', point); + obj.set('construct', 'line'); + obj.set('seq', i); + line.push(obj); + } + Parse.Object.saveAll(line).then(() => { + let query = new Parse.Query(TestObject); + let point = new Parse.GeoPoint(24, 19); + query.equalTo('construct', 'line'); + query.withinMiles('location', point, 10000); + return query.find(); + }).then((results) => { + assert.equal(results.length, 10); + assert.equal(results[0].get('seq'), 9); + assert.equal(results[3].get('seq'), 6); + done(); + }); + }); + + it('can query within large distances', (done) => { + let objects = []; + for (let i = 0; i < 3; i++) { + let obj = new TestObject(); + let point = new Parse.GeoPoint(0, i * 45); + obj.set('location', point); + obj.set('construct', 'large_dist') + obj.set('index', i); + objects.push(obj); + } + Parse.Object.saveAll(objects).then(() => { + let query = new Parse.Query(TestObject); + let point = new Parse.GeoPoint(1, -1); + query.equalTo('construct', 'large_dist'); + query.withinRadians('location', point, 3.14); + return query.find(); + }).then((results) => { + assert.equal(results.length, 3); + done(); + }); + }); + + it('can query within medium distances', (done) => { + let objects = []; + for (let i = 0; i < 3; i++) { + let obj = new TestObject(); + let point = new Parse.GeoPoint(0, i * 45); + obj.set('location', point); + obj.set('construct', 'medium_dist') + obj.set('index', i); + objects.push(obj); + } + Parse.Object.saveAll(objects).then(() => { + let query = new Parse.Query(TestObject); + let point = new Parse.GeoPoint(1, -1); + query.equalTo('construct', 'medium_dist'); + query.withinRadians('location', point, 3.14 * 0.5); + return query.find(); + }).then((results) => { + assert.equal(results.length, 2); + assert.equal(results[0].get('index'), 0); + assert.equal(results[1].get('index'), 1); + done(); + }); + }); + + it('can query within small distances', (done) => { + let objects = []; + for (let i = 0; i < 3; i++) { + let obj = new TestObject(); + let point = new Parse.GeoPoint(0, i * 45); + obj.set('location', point); + obj.set('construct', 'small_dist') + obj.set('index', i); + objects.push(obj); + } + Parse.Object.saveAll(objects).then(() => { + let query = new Parse.Query(TestObject); + let point = new Parse.GeoPoint(1, -1); + query.equalTo('construct', 'small_dist'); + query.withinRadians('location', point, 3.14 * 0.25); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('index'), 0); + done(); + }); + }); + + it('can measure distance within km - everywhere', (done) => { + let sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + let query = new Parse.Query(TestPoint); + query.withinKilometers('location', sfo, 4000.0); + query.find().then((results) => { + assert.equal(results.length, 3); + done(); + }); + }); + + it('can measure distance within km - california', (done) => { + let sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + let query = new Parse.Query(TestPoint); + query.withinKilometers('location', sfo, 3700.0); + query.find().then((results) => { + assert.equal(results.length, 2); + assert.equal(results[0].get('name'), 'San Francisco'); + assert.equal(results[1].get('name'), 'Sacramento'); + done(); + }); + }); + + it('can measure distance within km - bay area', (done) => { + let sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + let query = new Parse.Query(TestPoint); + query.withinKilometers('location', sfo, 100.0); + query.find().then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('name'), 'San Francisco'); + done(); + }); + }); + + it('can measure distance within km - mid peninsula', (done) => { + let sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + let query = new Parse.Query(TestPoint); + query.withinKilometers('location', sfo, 10.0); + query.find().then((results) => { + assert.equal(results.length, 0); + done(); + }); + }); + + it('can measure distance within miles - everywhere', (done) => { + let sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + let query = new Parse.Query(TestPoint); + query.withinMiles('location', sfo, 2500.0); + query.find().then((results) => { + assert.equal(results.length, 3); + done(); + }); + }); + + it('can measure distance within miles - california', (done) => { + let sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + let query = new Parse.Query(TestPoint); + query.withinMiles('location', sfo, 2200.0); + query.find().then((results) => { + assert.equal(results.length, 2); + assert.equal(results[0].get('name'), 'San Francisco'); + assert.equal(results[1].get('name'), 'Sacramento'); + done(); + }); + }); + + it('can measure distance within miles - bay area', (done) => { + let sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + let query = new Parse.Query(TestPoint); + query.withinMiles('location', sfo, 75.0); + query.find().then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('name'), 'San Francisco'); + done(); + }); + }); + + it('can measure distance within km - mid peninsula', (done) => { + let sfo = new Parse.GeoPoint(37.6189722, -122.3748889); + let query = new Parse.Query(TestPoint); + query.withinMiles('location', sfo, 10.0); + query.find().then((results) => { + assert.equal(results.length, 0); + done(); + }); + }); +}); \ No newline at end of file diff --git a/integration/test/ParseMasterKeyTest.js b/integration/test/ParseMasterKeyTest.js new file mode 100644 index 000000000..3fd119cef --- /dev/null +++ b/integration/test/ParseMasterKeyTest.js @@ -0,0 +1,51 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); + +describe('Master Key', () => { + before((done) => { + Parse.initialize('integration', null, 'notsosecret'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear().then(() => { + done(); + }); + }); + + it('can perform a simple save', (done) => { + let object = new TestObject(); + object.set('color', 'purple'); + object.save(null, { useMasterKey: true }).then((obj) => { + assert(object.id); + done(); + }); + }); + + it('can perform a save without permissions', (done) => { + let object; + Parse.User.signUp('andrew', 'password').then((user) => { + object = new TestObject({ ACL: new Parse.ACL(user) }); + return object.save(); + }).then(() => { + Parse.User.logOut(); + return object.save(null, { useMasterKey: true }); + }).then(() => { + // expect success + done(); + }).fail((e) => console.log(e)); + }); + + it('throws when no master key is provided', (done) => { + Parse.CoreManager.set('MASTER_KEY', null); + let object = new TestObject(); + object.save(null, { useMasterKey: true }).fail(() => { + // should fail + done(); + }); + }); +}); \ No newline at end of file diff --git a/integration/test/ParseObjectTest.js b/integration/test/ParseObjectTest.js new file mode 100644 index 000000000..f2aa56204 --- /dev/null +++ b/integration/test/ParseObjectTest.js @@ -0,0 +1,1333 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); +const Item = Parse.Object.extend('Item'); +const Container = Parse.Object.extend('Container'); + +describe('Parse Object', () => { + before((done) => { + Parse.initialize('integration', null, 'notsosecret'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear().then(() => { + done(); + }); + }); + + it('can create objects', (done) => { + let object = new TestObject({ test: 'test' }); + object.save().then((o) => { + assert(o); + assert(o.id); + assert.equal(o.get('test'), 'test'); + done(); + }); + }); + + it('can update objects', (done) => { + let object = new TestObject({ test: 'test' }); + object.save().then((o) => { + let o2 = new TestObject({ objectId: o.id }); + o2.set('test', 'changed'); + return o2.save(); + }).then((o) => { + assert.equal(o.get('test'), 'changed'); + done(); + }); + }); + + it('can save a cycle', (done) => { + let a = new TestObject(); + let b = new TestObject(); + a.set('b', b); + a.save().then(() => { + b.set('a', a); + return b.save(); + }).then(() => { + assert(a.id); + assert(b.id); + assert.equal(a.get('b').id, b.id); + assert.equal(b.get('a').id, a.id); + done(); + }); + }); + + it('can get objects', (done) => { + let object = new TestObject({ test: 'test' }); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((o) => { + assert.equal(o.get('test'), 'test'); + assert.equal(o.id, object.id); + done(); + }); + }); + + it('can delete an object', (done) => { + let object = new TestObject({ test: 'test' }); + object.save().then(() => { + return object.destroy(); + }).then(() => { + return object.fetch(); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('can find objects', (done) => { + let object = new TestObject({ foo: 'bar' }); + object.save().then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('can establish relational fields', (done) => { + let item = new Item(); + item.set('property', 'x'); + let container = new Container(); + container.set('item', item); + + Parse.Object.saveAll([item, container]).then(() => { + let query = new Parse.Query(Container); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + let containerAgain = results[0]; + let itemAgain = containerAgain.get('item'); + return itemAgain.fetch(); + }).then((itemAgain) => { + assert.equal(itemAgain.get('property'), 'x'); + done(); + }); + }); + + it('adds no fields on save (beyond createdAt and updatedAt)', (done) => { + let object = new TestObject(); + object.save().then(() => { + let attributes = object.attributes; + assert(attributes.createdAt); + assert(attributes.updatedAt); + let keys = Object.keys(attributes); + assert.equal(keys.length, 2); + done(); + }); + }); + + it('can perform a recursive save', (done) => { + let item = new Item(); + item.set('property', 'x'); + let container = new Container(); + container.set('item', item); + + container.save().then(() => { + let query = new Parse.Query(Container); + return query.get(container.id); + }).then((result) => { + assert(result); + let containerAgain = result; + let itemAgain = containerAgain.get('item'); + return itemAgain.fetch(); + }).then((itemAgain) => { + assert.equal(itemAgain.get('property'), 'x'); + done(); + }); + }); + + it('can fetch server data', (done) => { + let item = new Item({ foo: 'bar' }); + item.save().then(() => { + let itemAgain = new Item(); + itemAgain.id = item.id; + return itemAgain.fetch(); + }).then((itemAgain) => { + assert.equal(itemAgain.get('foo'), 'bar'); + return itemAgain.save({ foo: 'baz' }); + }).then(() => { + return item.fetch(); + }).then((itemAgain) => { + assert.equal(item.get('foo'), itemAgain.get('foo')); + done(); + }).fail(e => console.log(e)); + }); + + it('does not remove old fields on fetch', (done) => { + let object = new Parse.Object('SimpleObject'); + object.set('foo', 'bar'); + object.set('test', 'foo'); + let object1 = null; + let object2 = null; + object.save().then(() => { + let query = new Parse.Query('SimpleObject'); + return query.get(object.id); + }).then((o) => { + object1 = o; + let query = new Parse.Query('SimpleObject'); + return query.get(object.id); + }).then((o) => { + object2 = o; + assert.equal(object1.get('test'), 'foo'); + assert.equal(object2.get('test'), 'foo'); + object2.unset('test'); + return object2.save(); + }).then((o) => { + object2 = o; + return object1.fetch(); + }).then((o) => { + object1 = o; + assert.equal(object2.get('test'), undefined); + assert.equal(object2.get('foo'), 'bar'); + assert.equal(object1.get('test'), undefined); + assert.equal(object1.get('foo'), 'bar'); + done(); + }); + }); + + it('does not change createdAt', (done) => { + let object = new TestObject({ foo: 'bar' }); + object.save().then(() => { + let objectAgain = new TestObject(); + objectAgain.id = object.id; + return objectAgain.fetch(); + }).then((o) => { + assert.equal(o.createdAt.getTime(), object.createdAt.getTime()); + done(); + }); + }); + + it('exposes createdAt and updatedAt as top level properties', (done) => { + let object = new TestObject({ foo: 'bar' }); + object.save().then(() => { + assert(object.updatedAt); + assert(object.createdAt); + done(); + }); + }); + + it('produces a reasonable createdAt time', (done) => { + let start = new Date(); + let object = new TestObject({ foo: 'bar' }); + object.save().then(() => { + let end = new Date(); + let startDiff = Math.abs(start.getTime() - object.createdAt.getTime()); + let endDiff = Math.abs(end.getTime() - object.createdAt.getTime()); + done(); + }); + }); + + it('can set keys to null', (done) => { + let obj = new TestObject(); + obj.set('foo', null); + obj.save().then(() => { + assert.equal(obj.get('foo'), null); + done(); + }); + }); + + it('can set boolean fields', (done) => { + let obj = new TestObject(); + obj.set('yes', true); + obj.set('no', false); + obj.save().then(() => { + assert.equal(obj.get('yes'), true); + assert.equal(obj.get('no'), false); + done(); + }); + }); + + it('cannot set an invalid date', (done) => { + let obj = new TestObject(); + obj.set('when', new Date(Date.parse(null))); + obj.save().fail((e) => { + done(); + }); + }); + + it('cannot create invalid class names', (done) => { + let item = new Parse.Object('Foo^Bar'); + item.save().fail((e) => { + done(); + }); + }); + + it('cannot create invalid key names', (done) => { + let item = new Parse.Object('Item'); + assert(!item.set({ 'foo^bar': 'baz' })); + item.save({ 'foo^bar': 'baz' }).fail((e) => { + assert.equal(e.code, Parse.Error.INVALID_KEY_NAME); + done(); + }); + }); + + it('cannot use invalid key names in multiple sets', () => { + let item = new Parse.Object('Item'); + assert(!item.set({ + 'foobar': 'baz', + 'foo^bar': 'baz' + })); + assert(!item.get('foobar')); + }); + + it('can unset fields', (done) => { + let simple = new Parse.Object('SimpleObject'); + simple.save({ foo: 'bar' }).then(() => { + simple.unset('foo'); + assert(!simple.has('foo')); + assert(simple.dirty('foo')); + assert(simple.dirty()); + return simple.save(); + }).then(() => { + assert(!simple.has('foo')); + assert(!simple.dirty('foo')); + assert(!simple.dirty()); + + let query = new Parse.Query('SimpleObject'); + return query.get(simple.id); + }).then((s) => { + assert(!s.has('foo')); + done(); + }); + }); + + it('can delete fields before the first save', (done) => { + let simple = new Parse.Object('SimpleObject'); + simple.set('foo', 'bar'); + simple.unset('foo'); + + assert(!simple.has('foo')); + assert(simple.dirty('foo')); + assert(simple.dirty()); + simple.save().then(() => { + assert(!simple.has('foo')); + assert(!simple.dirty('foo')); + assert(!simple.dirty()); + + let query = new Parse.Query('SimpleObject'); + return query.get(simple.id); + }).then((s) => { + assert(!s.has('foo')); + done(); + }); + }); + + it('can delete pointers', (done) => { + let simple = new Parse.Object('SimpleObject'); + let child = new Parse.Object('Child'); + simple.save({ child: child }).then(() => { + simple.unset('child'); + assert(!simple.has('child')); + assert(simple.dirty('child')); + assert(simple.dirty()); + return simple.save(); + }).then(() => { + assert(!simple.has('child')); + assert(!simple.dirty('child')); + assert(!simple.dirty()); + + let query = new Parse.Query('SimpleObject'); + return query.get(simple.id); + }).then((s) => { + assert(!s.has('foo')); + done(); + }); + }); + + it('clears deleted keys', (done) => { + let simple = new Parse.Object('SimpleObject'); + simple.set('foo', 'bar'); + simple.unset('foo'); + simple.save().then(() => { + simple.set('foo', 'baz'); + return simple.save(); + }).then(() => { + let query = new Parse.Query('SimpleObject'); + return query.get(simple.id); + }).then((s) => { + assert.equal(s.get('foo'), 'baz'); + done(); + }); + }); + + it('can set keys after deleting them', (done) => { + let simple = new Parse.Object('SimpleObject'); + simple.set('foo', 'bar') + simple.save().then(() => { + simple.unset('foo'); + simple.set('foo', 'baz'); + return simple.save(); + }).then(() => { + let query = new Parse.Query('SimpleObject'); + return query.get(simple.id); + }).then((s) => { + assert.equal(s.get('foo'), 'baz'); + done(); + }); + }); + + it('can increment fields', (done) => { + let simple = new Parse.Object('SimpleObject'); + simple.save({ count: 5 }).then(() => { + simple.increment('count'); + assert.equal(simple.get('count'), 6); + assert(simple.dirty('count')); + assert(simple.dirty()); + return simple.save(); + }).then(() => { + assert.equal(simple.get('count'), 6); + assert(!simple.dirty('count')); + assert(!simple.dirty()); + + let query = new Parse.Query('SimpleObject'); + return query.get(simple.id); + }).then((s) => { + assert.equal(s.get('count'), 6); + done(); + }); + }); + + it('can set the object id', () => { + let object = new TestObject(); + object.set('objectId', 'foo'); + assert.equal(object.id, 'foo'); + object.set('id', 'bar'); + assert.equal(object.id, 'bar'); + }); + + it('can mark dirty attributes', (done) => { + let object = new TestObject(); + object.set('cat', 'goog'); + object.set('dog', 'bad'); + object.save().then(() => { + assert(!object.dirty()); + assert(!object.dirty('cat')); + assert(!object.dirty('dog')); + + object.set('dog', 'okay'); + + assert(object.dirty()); + assert(!object.dirty('cat')); + assert(object.dirty('dog')); + + done(); + }); + }); + + it('can collect dirty keys', (done) => { + let object = new TestObject(); + object.set('dog', 'good'); + object.set('cat', 'bad'); + assert(object.dirty()); + let dirtyKeys = object.dirtyKeys(); + assert.equal(dirtyKeys.length, 2); + assert(dirtyKeys.indexOf('dog') > -1); + assert(dirtyKeys.indexOf('cat') > -1); + + object.save().then(() => { + assert(!object.dirty()); + dirtyKeys = object.dirtyKeys(); + assert.equal(dirtyKeys.length, 0); + assert(dirtyKeys.indexOf('dog') < 0); + assert(dirtyKeys.indexOf('cat') < 0); + + object.unset('cat'); + assert(object.dirty()); + dirtyKeys = object.dirtyKeys(); + assert.equal(dirtyKeys.length, 1); + assert(dirtyKeys.indexOf('dog') < 0); + assert(dirtyKeys.indexOf('cat') > -1); + + return object.save(); + }).then(() => { + assert(!object.dirty()); + assert.equal(object.get('dog'), 'good'); + assert.equal(object.get('cat'), undefined); + dirtyKeys = object.dirtyKeys(); + assert.equal(dirtyKeys.length, 0); + assert(dirtyKeys.indexOf('dog') < 0); + assert(dirtyKeys.indexOf('cat') < 0); + done(); + }); + }); + + it('can set ops directly', (done) => { + let object = new Parse.Object('TestObject'); + object.set({ cat: 'good', dog: 'bad' }); + object.save().then(() => { + assert.equal(object.get('cat'), 'good'); + + object.set({ x: { __op: 'Increment', amount: 5 }}); + assert.equal(object.get('x'), 5); + assert(object.dirty()); + assert(object.dirty('x')); + assert(!object.dirty('cat')); + assert(object.op('x') instanceof Parse.Op.Increment); + assert.equal(object.op('x')._amount, 5); + + object.set({ x: { __op: 'Increment', amount: 2 }}); + assert.equal(object.get('x'), 7); + assert(object.op('x') instanceof Parse.Op.Increment); + assert.equal(object.op('x')._amount, 7); + + object.set({ cat: { __op: 'Delete' }}); + assert(!object.has('cat')); + assert(object.op('cat') instanceof Parse.Op.Unset); + + let Related = new Parse.Object.extend('RelatedObject'); + var relatedObjects = []; + for (let i = 0; i < 5; i++) { + relatedObjects.push(new Related({ i: i })); + } + Parse.Object.saveAll(relatedObjects).then(() => { + object.set({ + relation: { + __op: 'Batch', + ops: [{ + __op: 'AddRelation', + objects: [relatedObjects[0], relatedObjects[1]] + }, { + __op: 'AddRelation', + objects: [relatedObjects[2], relatedObjects[3]] + }] + } + }); + let relation = object.op('relation'); + assert(relation instanceof Parse.Op.Relation); + assert.equal(relation.relationsToAdd.length, 4); + + object.set({ + relation: { + __op: 'RemoveRelation', + objects: [relatedObjects[1], relatedObjects[4]] + } + }); + + relation = object.op('relation'); + assert(relation instanceof Parse.Op.Relation); + assert.equal(relation.relationsToAdd.length, 3); + assert.equal(relation.relationsToRemove.length, 2); + + done(); + }); + }); + }); + + it('can repeatedly unset old attributes', (done) => { + let obj = new TestObject(); + obj.set('x', 3); + obj.save().then(() => { + obj.unset('x'); + obj.unset('x'); + return obj.save(); + }).then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can repeatedly unset new attributes', (done) => { + let obj = new TestObject(); + obj.set('x', 5); + obj.unset('x'); + obj.unset('x'); + obj.save().then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can repeatedly unset an unknown attributes', (done) => { + let obj = new TestObject(); + obj.unset('x'); + obj.unset('x'); + obj.save().then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can unset then clear old attributes', (done) => { + let obj = new TestObject(); + obj.set('x', 3); + obj.save().then(() => { + obj.unset('x'); + obj.clear(); + return obj.save(); + }).then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can unset then clear new attributes', (done) => { + let obj = new TestObject(); + obj.set('x', 5); + obj.unset('x'); + obj.clear(); + obj.save().then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can unset then clear unknown attributes', (done) => { + let obj = new TestObject(); + obj.unset('x'); + obj.clear(); + obj.save().then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can clear then unset old attributes', (done) => { + let obj = new TestObject(); + obj.set('x', 3); + obj.save().then(() => { + obj.clear(); + obj.unset('x'); + return obj.save(); + }).then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can clear then unset new attributes', (done) => { + let obj = new TestObject(); + obj.set('x', 5); + obj.clear(); + obj.unset('x'); + obj.save().then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can clear then unset unknown attributes', (done) => { + let obj = new TestObject(); + obj.clear(); + obj.unset('x'); + obj.save().then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can clear then clear old attributes', (done) => { + let obj = new TestObject(); + obj.set('x', 3); + obj.save().then(() => { + obj.clear(); + obj.clear(); + return obj.save(); + }).then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can clear then clear new attributes', (done) => { + let obj = new TestObject(); + obj.set('x', 5); + obj.clear(); + obj.clear(); + obj.save().then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can clear then clear unknown attributes', (done) => { + let obj = new TestObject(); + obj.clear(); + obj.clear(); + obj.save().then(() => { + assert.equal(obj.has('x'), false); + assert.equal(obj.get('x'), undefined); + let query = new Parse.Query(TestObject); + return query.get(obj.id); + }).then((o) => { + assert.equal(o.has('x'), false); + assert.equal(o.get('x'), undefined); + done(); + }); + }); + + it('can save children in an array', (done) => { + let Parent = Parse.Object.extend('Parent'); + let Child = Parse.Object.extend('Child'); + + let child1 = new Child(); + let child2 = new Child(); + let parent = new Parent(); + + child1.set('name', 'jaime'); + child1.set('house', 'lannister'); + child2.set('name', 'cersei'); + child2.set('house', 'lannister'); + parent.set('children', [child1, child2]); + + parent.save().then(() => { + let query = new Parse.Query(Child); + query.equalTo('house', 'lannister'); + query.ascending('name'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 2); + assert.equal(results[0].get('name'), 'cersei'); + assert.equal(results[1].get('name'), 'jaime'); + done(); + }); + }); + + it('can do two saves at the same time', (done) => { + let object = new TestObject(); + let firstSave = true; + + let success = () => { + if (firstSave) { + firstSave = false; + return; + } + + let query = new Parse.Query('TestObject'); + query.equalTo('test', 'doublesave'); + query.find().then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('cat'), 'meow'); + assert.equal(results[0].get('dog'), 'bark'); + done(); + }); + } + + object.save({ cat: 'meow', test: 'doublesave' }).then(success); + object.save({ dog: 'bark', test: 'doublesave' }).then(success); + }); + + it('can achieve a save after failure', (done) => { + let object = new TestObject(); + let other; + object.set('number', 1); + object.save().then(() => { + other = new TestObject(); + other.set('number', 'two'); + return other.save(); + }).fail((e) => { + assert.equal(e.code, Parse.Error.INCORRECT_TYPE); + other.set('number', 2); + return other.save(); + }).then(() => { + done(); + }); + }); + + it('is not dirty after save', (done) => { + let object = new TestObject(); + object.save().then(() => { + object.set({ content: 'x' }); + assert(object.dirty('content')); + return object.fetch(); + }).then(() => { + assert(!object.dirty('content')); + done(); + }); + }); + + it('can add objects to an array', (done) => { + let child = new Parse.Object('Person'); + let parent = new Parse.Object('Person'); + + child.save().then(() => { + parent.add('children', child); + return parent.save(); + }).then(() => { + let query = new Parse.Query('Person'); + return query.get(parent.id); + }).then((p) => { + assert.equal(p.get('children')[0].id, child.id); + done(); + }); + }); + + it('can convert saved objects to json', (done) => { + let object = new TestObject(); + object.save({ foo: 'bar' }).then(() => { + let json = object.toJSON(); + assert(json.foo); + assert(json.objectId); + assert(json.createdAt); + assert(json.updatedAt); + done(); + }); + }); + + it('can convert unsaved objects to json', () => { + let object = new TestObject(); + object.set({ foo: 'bar' }); + let json = object.toJSON(); + assert(json.foo); + assert(!json.objectId); + assert(!json.createdAt); + assert(!json.updatedAt); + }); + + it('can remove objects from array fields', (done) => { + let object = new TestObject(); + let container; + object.save().then(() => { + container = new TestObject(); + container.add('array', object); + assert.equal(container.get('array').length, 1); + return container.save(); + }).then(() => { + let o = new TestObject(); + o.id = object.id; + container.remove('array', o); + assert.equal(container.get('array').length, 0); + done(); + }); + }); + + it('can perform async methods', (done) => { + let object = new TestObject(); + object.set('time', 'adventure'); + object.save().then(() => { + assert(object.id); + let again = new TestObject(); + again.id = object.id; + return again.fetch(); + }).then((again) => { + assert.equal(again.get('time'), 'adventure'); + return again.destroy(); + }).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + return query.find(); + }).then((results) => { + assert.equal(results.length, 0); + done(); + }); + }); + + it('fails validation with a promise', (done) => { + let PickyEater = Parse.Object.extend('PickyEater', { + validate: function(attrs) { + if (attrs.meal === 'tomatoes') { + return 'Ew. Gross.'; + } + return Parse.Object.prototype.validate.apply(this, arguments); + } + }); + + let bryan = new PickyEater(); + bryan.save({ meal: 'burrito' }).then(() => { + return bryan.save({ meal: 'tomatoes' }); + }).fail((e) => { + assert.equal(e, 'Ew. Gross.'); + done(); + }); + }); + + it('works with bytes type', (done) => { + let object = new TestObject(); + object.set('bytes', { __type: 'Bytes', base64: 'ZnJveW8=' }); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id) + }).then((o) => { + assert.equal(o.get('bytes').__type, 'Bytes'); + assert.equal(o.get('bytes').base64, 'ZnJveW8='); + done(); + }); + }); + + it('can destroyAll with no objects', (done) => { + Parse.Object.destroyAll([]).then(() => { + done(); + }); + }); + + it('can destroyAll unsaved objects', (done) => { + let objects = [new TestObject(), new TestObject()]; + Parse.Object.destroyAll(objects).then(() => { + done(); + }); + }); + + it('can destroyAll a single object', (done) => { + let o = new TestObject(); + o.save().then(() => { + return Parse.Object.destroyAll([o]); + }).then(() => { + let query = new Parse.Query(TestObject); + return query.get(o.id); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('can destroyAll two batches', (done) => { + let objects = []; + for (let i = 0; i < 21; i++) { + objects[i] = new TestObject(); + } + Parse.Object.saveAll(objects).then(() => { + return Parse.Object.destroyAll(objects); + }).then(() => { + let query = new Parse.Query(TestObject); + query.containedIn('objectId', [objects[0].id, objects[20].id]); + return query.find(); + }).then((results) => { + assert.equal(results.length, 0); + done(); + }); + }); + + it('can destroyAll an object that does not exist', (done) => { + let o = new TestObject(); + o.id = 'fakeobject'; + Parse.Object.destroyAll([o]).fail((e) => { + assert.equal(e.code, Parse.Error.AGGREGATE_ERROR); + assert.equal(e.errors.length, 1); + done(); + }); + }); + + it('can destroyAll two batches when the first object does not exist', (done) => { + let objects = []; + for (let i = 0; i < 21; i++) { + objects[i] = new TestObject(); + } + Parse.Object.saveAll(objects).then(() => { + objects[0].id = 'fakeobject'; + return Parse.Object.destroyAll(objects); + }).fail((e) => { + assert.equal(e.code, Parse.Error.AGGREGATE_ERROR); + assert.equal(e.errors.length, 1); + assert.equal(e.errors[0].code, Parse.Error.OBJECT_NOT_FOUND); + assert.equal(e.errors[0].object, objects[0]); + done(); + }); + }); + + it('can destroyAll two batches when a middle object does not exist', (done) => { + let objects = []; + for (let i = 0; i < 21; i++) { + objects[i] = new TestObject(); + } + Parse.Object.saveAll(objects).then(() => { + objects[19].id = 'fakeobject'; + return Parse.Object.destroyAll(objects); + }).fail((e) => { + assert.equal(e.code, Parse.Error.AGGREGATE_ERROR); + assert.equal(e.errors.length, 1); + assert.equal(e.errors[0].code, Parse.Error.OBJECT_NOT_FOUND); + assert.equal(e.errors[0].object, objects[19]); + done(); + }); + }); + + it('can destroyAll two batches when the last object does not exist', (done) => { + let objects = []; + for (let i = 0; i < 21; i++) { + objects[i] = new TestObject(); + } + Parse.Object.saveAll(objects).then(() => { + objects[20].id = 'fakeobject'; + return Parse.Object.destroyAll(objects); + }).fail((e) => { + assert.equal(e.code, Parse.Error.AGGREGATE_ERROR); + assert.equal(e.errors.length, 1); + assert.equal(e.errors[0].code, Parse.Error.OBJECT_NOT_FOUND); + assert.equal(e.errors[0].object, objects[20]); + done(); + }); + }); + + it('can destroyAll two batches with multiple missing objects', (done) => { + let objects = []; + for (let i = 0; i < 21; i++) { + objects[i] = new TestObject(); + } + Parse.Object.saveAll(objects).then(() => { + objects[0].id = 'fakeobject'; + objects[19].id = 'fakeobject'; + objects[20].id = 'fakeobject'; + return Parse.Object.destroyAll(objects); + }).fail((e) => { + assert.equal(e.code, Parse.Error.AGGREGATE_ERROR); + assert.equal(e.errors.length, 3); + assert.equal(e.errors[0].code, Parse.Error.OBJECT_NOT_FOUND); + assert.equal(e.errors[1].code, Parse.Error.OBJECT_NOT_FOUND); + assert.equal(e.errors[2].code, Parse.Error.OBJECT_NOT_FOUND); + assert.equal(e.errors[0].object, objects[0]); + assert.equal(e.errors[1].object, objects[19]); + assert.equal(e.errors[2].object, objects[20]); + done(); + }); + }); + + it('can fetchAll', (done) => { + let numItems = 11; + let container = new Container(); + let items = []; + for (let i = 0; i < numItems; i++) { + let item = new Item(); + item.set('x', i); + items.push(item); + } + Parse.Object.saveAll(items).then(() => { + container.set('items', items); + return container.save(); + }).then(() => { + let query = new Parse.Query(Container); + return query.get(container.id); + }).then((containerAgain) => { + let itemsAgain = containerAgain.get('items'); + assert.equal(itemsAgain.length, numItems); + itemsAgain.forEach((item, i) => { + let newValue = i * 2; + item.set('x', newValue); + }); + return Parse.Object.saveAll(itemsAgain); + }).then(() => { + return Parse.Object.fetchAll(items); + }).then((itemsAgain) => { + assert.equal(itemsAgain.length, numItems); + itemsAgain.forEach((item, i) => { + assert.equal(item.get('x'), i * 2); + }); + done(); + }); + }); + + it('can fetchAll with no objects', (done) => { + Parse.Object.fetchAll([]).then(() => { + done(); + }); + }); + + it('updates dates on fetchAll', (done) => { + let updated; + let object = new TestObject(); + object.set('x', 7); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((result) => { + updated = result; + updated.set('x', 11); + return updated.save(); + }).then(() => { + return Parse.Object.fetchAll([object]); + }).then(() => { + assert.equal(object.createdAt.getTime(), updated.createdAt.getTime()); + assert.equal(object.updatedAt.getTime(), updated.updatedAt.getTime()); + done(); + }); + }); + + it('fails fetchAll on multiple classes', (done) => { + let container = new Container(); + container.set('item', new Item()); + container.set('subcontainer', new Container()); + return container.save().then(() => { + let query = new Parse.Query(Container); + return query.get(container.id); + }).then((containerAgain) => { + let subContainerAgain = containerAgain.get('subcontainer'); + let itemAgain = containerAgain.get('item'); + let multiClassArray = [subContainerAgain, itemAgain]; + return Parse.Object.fetchAll(multiClassArray); + }).fail((e) => { + assert.equal(e.code, Parse.Error.INVALID_CLASS_NAME); + done(); + }); + }); + + it('fails fetchAll on unsaved object', (done) => { + let unsavedObjectArray = [new TestObject()]; + Parse.Object.fetchAll(unsavedObjectArray).fail((e) => { + assert.equal(e.code, Parse.Error.MISSING_OBJECT_ID); + done(); + }); + }); + + it('fails fetchAll on deleted object', (done) => { + let numItems = 11; + let container = new Container(); + let subContainer = new Container(); + let items = []; + for (let i = 0; i < numItems; i++) { + let item = new Item(); + item.set('x', i); + items.push(item); + } + Parse.Object.saveAll(items).then(() => { + let query = new Parse.Query(Item); + return query.get(items[0].id); + }).then((objectToDelete) => { + return objectToDelete.destroy(); + }).then((deletedObject) => { + let nonExistentObject = new Item({ objectId: deletedObject.id }); + let nonExistentObjectArray = [nonExistentObject, items[1]]; + return Parse.Object.fetchAll(nonExistentObjectArray); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('merges user attributes on fetchAll', (done) => { + Parse.User.enableUnsafeCurrentUser(); + let sameUser; + let user = new Parse.User(); + user.set('username', 'asdf'); + user.set('password', 'zxcv'); + user.set('foo', 'bar'); + user.signUp().then(() => { + Parse.User.logOut(); + let query = new Parse.Query(Parse.User); + return query.get(user.id); + }).then((userAgain) => { + user = userAgain; + sameUser = new Parse.User(); + sameUser.set('username', 'asdf'); + sameUser.set('password', 'zxcv'); + return sameUser.logIn(); + }).then(() => { + assert(!user.getSessionToken()); + assert(sameUser.getSessionToken()); + sameUser.set('baz', 'qux'); + return sameUser.save(); + }).then(() => { + return Parse.Object.fetchAll([user]); + }).then(() => { + assert.equal(user.createdAt.getTime(), sameUser.createdAt.getTime()); + assert.equal(user.updatedAt.getTime(), sameUser.updatedAt.getTime()); + done(); + }); + }); + + it('can fetchAllIfNeeded', (done) => { + let numItems = 11; + let container = new Container(); + let items = []; + for (let i = 0; i < numItems; i++) { + let item = new Item(); + item.set('x', i); + items.push(item); + } + Parse.Object.saveAll(items).then(() => { + container.set('items', items); + return container.save(); + }).then(() => { + let query = new Parse.Query(Container); + return query.get(container.id); + }).then((containerAgain) => { + let itemsAgain = containerAgain.get('items'); + itemsAgain.forEach((item, i) => { + item.set('x', i * 2); + }); + return Parse.Object.saveAll(itemsAgain); + }).then(() => { + return Parse.Object.fetchAllIfNeeded(items); + }).then((fetchedItems) => { + assert.equal(fetchedItems.length, numItems); + fetchedItems.forEach((item, i) => { + assert.equal(item.get('x'), i); + }); + done(); + }); + }); + + it('can fetchAllIfNeeded with no objects', (done) => { + Parse.Object.fetchAllIfNeeded([]).then(() => { + done(); + }); + }); + + it('can fetchAllIfNeeded with an unsaved object', () => { + let unsavedObjectArray = [new TestObject()]; + Parse.Object.fetchAllIfNeeded(unsavedObjectArray).fail((e) => { + assert.equal(e.code, Parse.Error.MISSING_OBJECT_ID); + done(); + }); + }); + + it('fails fetchAllIfNeeded on multiple classes', (done) => { + let container = new Container(); + container.set('item', new Item()); + container.set('subcontainer', new Container()); + return container.save().then(() => { + let query = new Parse.Query(Container); + return query.get(container.id); + }).then((containerAgain) => { + let subContainerAgain = containerAgain.get('subcontainer'); + let itemAgain = containerAgain.get('item'); + let multiClassArray = [subContainerAgain, itemAgain]; + return Parse.Object.fetchAllIfNeeded(multiClassArray); + }).fail((e) => { + assert.equal(e.code, Parse.Error.INVALID_CLASS_NAME); + done(); + }); + }); + + it('can rewrite the User classname', (done) => { + assert.equal(Parse.CoreManager.get('PERFORM_USER_REWRITE'), true); + let User1 = Parse.Object.extend({ + className: 'User' + }); + + assert.equal(User1.className, '_User'); + + Parse.User.allowCustomUserClass(true); + assert.equal(Parse.CoreManager.get('PERFORM_USER_REWRITE'), false); + let User2 = Parse.Object.extend({ + className: 'User' + }); + + assert.equal(User2.className, 'User'); + + Parse.User.allowCustomUserClass(false); + assert.equal(Parse.CoreManager.get('PERFORM_USER_REWRITE'), true); + + let user = new User2(); + user.set('name', 'Me'); + user.save({ height: 181 }).then(() => { + assert.equal(user.get('name'), 'Me'); + assert.equal(user.get('height'), 181); + + let query = new Parse.Query(User2); + return query.get(user.id); + }).then((u) => { + assert.equal(user.className, 'User'); + assert.equal(user.get('name'), 'Me'); + assert.equal(user.get('height'), 181); + + done(); + }); + }); + + it('can create objects without data', (done) => { + let t1 = new TestObject({ test: 'test' }); + t1.save().then(() => { + let t2 = TestObject.createWithoutData(t1.id); + return t2.fetch(); + }).then((t2) => { + assert.equal(t2.get('test'), 'test'); + let t3 = TestObject.createWithoutData(t2.id); + t3.set('test', 'not test'); + return t3.fetch(); + }).then((t3) => { + assert.equal(t3.get('test'), 'test'); + done(); + }); + }); + + it('fires errors when readonly attributes are changed', (done) => { + let LimitedObject = Parse.Object.extend('LimitedObject'); + LimitedObject.readOnlyAttributes = function() { + return ['immutable']; + }; + + let lo = new LimitedObject(); + try { + lo.set('immutable', 'mutable'); + } catch (e) { + done(); + } + }); + + it('fires errors when readonly attributes are unset', (done) => { + let LimitedObject = Parse.Object.extend('LimitedObject'); + LimitedObject.readOnlyAttributes = function() { + return ['immutable']; + }; + + let lo = new LimitedObject(); + try { + lo.unset('immutable'); + } catch (e) { + done(); + } + }); +}); diff --git a/integration/test/ParseQueryTest.js b/integration/test/ParseQueryTest.js new file mode 100644 index 000000000..b5736c4bf --- /dev/null +++ b/integration/test/ParseQueryTest.js @@ -0,0 +1,1424 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); + +describe('Parse Query', () => { + before((done) => { + Parse.initialize('integration', null, 'notsosecret'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear().then(() => { + let numbers = []; + for (let i = 0; i < 10; i++) { + numbers[i] = new Parse.Object({ className: 'BoxedNumber', number: i }); + } + return Parse.Object.saveAll(numbers); + }).then(() => { + done(); + }); + }); + + it('can do basic queries', (done) => { + let baz = new TestObject({ foo: 'baz' }); + let qux = new TestObject({ foo: 'qux' }); + Parse.Object.saveAll([baz, qux]).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('foo', 'baz'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('foo'), 'baz'); + done(); + }); + }); + + it('can do a query with a limit', () => { + let baz = new TestObject({ foo: 'baz' }); + let qux = new TestObject({ foo: 'qux' }); + Parse.Object.saveAll([baz, qux]).then(() => { + let query = new Parse.Query(TestObject); + query.limit(1); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('foo'), 'baz'); + done(); + }); + }); + + it('can do containedIn queries with arrays', (done) => { + let messageList = []; + for (let i = 0; i < 4; i++) { + let message = new Parse.Object('Message'); + if (i > 0) { + message.set('prior', messageList[i - 1]); + } + messageList.push(message); + } + + Parse.Object.saveAll(messageList).then(() => { + assert.equal(messageList.length, 4); + + let inList = []; + inList.push(messageList[0]); + inList.push(messageList[2]); + + let query = new Parse.Query('Message'); + query.containedIn('prior', inList); + return query.find(); + }).then((results) => { + assert.equal(results.length, 2); + done(); + }); + }); + + it('can do containsAll queries with numbers', (done) => { + let NumberSet = Parse.Object.extend('NumberSet'); + let objectsList = []; + objectsList.push(new NumberSet({ numbers: [1,2,3,4,5] })); + objectsList.push(new NumberSet({ numbers: [1,3,4,5] })); + Parse.Object.saveAll(objectsList).then(() => { + let query = new Parse.Query(NumberSet); + query.containsAll('numbers', [1,2,3]); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('can do containsAll queries with strings', (done) => { + let StringSet = Parse.Object.extend('StringSet'); + let objectsList = []; + objectsList.push(new StringSet({ strings: ['a', 'b', 'c', 'd', 'e'] })); + objectsList.push(new StringSet({ strings: ['a', 'c', 'd'] })); + Parse.Object.saveAll(objectsList).then(() => { + let query = new Parse.Query(StringSet); + query.containsAll('strings', ['a', 'b', 'c']); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('can do containsAll queries with dates', (done) => { + let DateSet = Parse.Object.extend('DateSet'); + + function parseDate(iso) { + let regexp = new RegExp( + '^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})' + 'T' + + '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})' + + '(.([0-9]+))?' + 'Z$'); + let match = regexp.exec(iso); + if (!match) { + return null; + } + + let year = match[1] || 0; + let month = (match[2] || 1) - 1; + let day = match[3] || 0; + let hour = match[4] || 0; + let minute = match[5] || 0; + let second = match[6] || 0; + let milli = match[8] || 0; + + return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); + } + + function makeDates(stringArray) { + return stringArray.map((date) => { + return parseDate(date + 'T00:00:00Z'); + }); + } + + let objectsList = []; + objectsList.push(new DateSet({ + dates: makeDates(['2013-02-01', '2013-02-02', '2013-02-03']) + })); + objectsList.push(new DateSet({ + dates: makeDates(['2013-02-01', '2013-02-03', '2013-02-04']) + })); + + Parse.Object.saveAll(objectsList).then(() => { + let query = new Parse.Query(DateSet); + query.containsAll('dates', makeDates( + ['2013-02-01', '2013-02-02', '2013-02-03'] + )); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }).fail(e => console.log(e)); + }); + + it('can do containsAll queries with objects', (done) => { + let MessageSet = Parse.Object.extend('MessageSet'); + + let messageList = []; + for (let i = 0; i < 4; i++) { + messageList.push(new TestObject({i: i})); + } + + Parse.Object.saveAll(messageList).then(() => { + assert.equal(messageList.length, 4); + + let messageSetList = []; + messageSetList.push(new MessageSet({messages: messageList})); + + let someList = []; + someList.push(messageList[0]); + someList.push(messageList[1]); + someList.push(messageList[3]); + messageSetList.push(new MessageSet({messages: someList})); + + return Parse.Object.saveAll(messageSetList); + }).then(() => { + let inList = []; + inList.push(messageList[0]); + inList.push(messageList[2]); + + let query = new Parse.Query(MessageSet); + query.containsAll('messages', inList); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('can do equalTo queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.equalTo('number', 3); + query.find().then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('can test equality with undefined', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.equalTo('number', undefined); + query.find().then((results) => { + assert.equal(results.length, 0); + done(); + }); + }); + + it('can perform lessThan queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.lessThan('number', 7); + query.find().then((results) => { + assert.equal(results.length, 7); + done(); + }); + }); + + it('can perform lessThanOrEqualTo queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.lessThanOrEqualTo('number', 7); + query.find().then((results) => { + assert.equal(results.length, 8); + done(); + }); + }); + + it('can perform greaterThan queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.greaterThan('number', 7); + query.find().then((results) => { + assert.equal(results.length, 2); + done(); + }); + }); + + it('can perform greaterThanOrEqualTo queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.greaterThanOrEqualTo('number', 7); + query.find().then((results) => { + assert.equal(results.length, 3); + done(); + }); + }); + + it('can combine lessThanOrEqualTo and greaterThanOrEqualTo queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.lessThanOrEqualTo('number', 7); + query.greaterThanOrEqualTo('number', 7); + query.find().then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('can combine lessThan and greaterThan queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.lessThan('number', 9); + query.greaterThan('number', 3); + query.find().then((results) => { + assert.equal(results.length, 5); + done(); + }); + }); + + it('can perform notEqualTo queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.notEqualTo('number', 5); + query.find().then((results) => { + assert.equal(results.length, 9); + done(); + }); + }); + + it('can perform containedIn queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.containedIn('number', [3,5,7,9,11]); + query.find().then((results) => { + assert.equal(results.length, 4); + done(); + }); + }); + + it('can perform notContainedIn queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.notContainedIn('number', [3,5,7,9,11]); + query.find().then((results) => { + assert.equal(results.length, 6); + done(); + }); + }); + + it('can test objectId in containedIn queries', (done) => { + new Parse.Query('BoxedNumber').ascending('number').find().then((numbers) => { + let ids = [numbers[2].id, numbers[3].id, 'nonsense']; + let query = new Parse.Query('BoxedNumber'); + query.containedIn('objectId', ids); + query.ascending('number'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 2); + assert.equal(results[0].get('number'), 2); + assert.equal(results[1].get('number'), 3); + done(); + }); + }); + + it('can test objectId in equalTo queries', (done) => { + new Parse.Query('BoxedNumber').ascending('number').find().then((numbers) => { + let id = numbers[5].id; + let query = new Parse.Query('BoxedNumber'); + query.equalTo('objectId', id); + query.ascending('number'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('number'), 5); + done(); + }); + }); + + it('can find no elements', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.equalTo('number', 15); + query.find().then((results) => { + assert.equal(results.length, 0); + done(); + }); + }); + + it('handles when find throws errors', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.equalTo('$foo', 'bar'); + query.find().fail((e) => { + assert.equal(e.code, Parse.Error.INVALID_KEY_NAME); + done(); + }); + }); + + it('can get by objectId', (done) => { + let object = new TestObject(); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(object.id); + }).then((result) => { + assert.equal(result.id, object.id); + assert(result.createdAt); + assert(result.updatedAt); + done(); + }); + }); + + it('handles get with undefined id', (done) => { + let object = new TestObject(); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(undefined); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('handles get with invalid id', (done) => { + let object = new TestObject(); + object.save().then(() => { + let query = new Parse.Query(TestObject); + return query.get(undefined); + }).fail((e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('can query for the first result', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.ascending('number'); + query.first().then((result) => { + assert.equal(result.get('number'), 0); + done(); + }); + }); + + it('can query for the first with no results', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.equalTo('number', 20); + query.first().then((result) => { + assert.equal(result, undefined); + done(); + }); + }); + + it('can query for the first with two results', (done) => { + Parse.Object.saveAll([new TestObject({x: 44}), new TestObject({x: 44})]).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('x', 44); + return query.first() + }).then((result) => { + assert.equal(result.get('x'), 44); + done(); + }); + }); + + it('handles when first throws errors', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.equalTo('$foo', 'bar'); + query.first().fail((e) => { + assert.equal(e.code, Parse.Error.INVALID_KEY_NAME); + done(); + }); + }); + + it('can test object inequality', (done) => { + let item1 = new TestObject(); + let item2 = new TestObject(); + let container1 = new Parse.Object({className: 'CoolContainer', item: item1}); + let container2 = new Parse.Object({className: 'CoolContainer', item: item2}); + Parse.Object.saveAll([item1, item2, container1, container2]).then(() => { + let query = new Parse.Query('CoolContainer'); + query.notEqualTo('item', item1); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('can skip', (done) => { + Parse.Object.saveAll([ + new TestObject({ canSkip: true }), + new TestObject({ canSkip: true }), + new TestObject({ canSkip: true }) + ]).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('canSkip', true); + query.skip(1); + return query.find(); + }).then((results) => { + assert.equal(results.length, 2); + let query = new Parse.Query(TestObject); + query.equalTo('canSkip', true); + query.skip(3); + return query.find(); + }).then((results) => { + assert.equal(results.length, 0); + done(); + }); + }); + + it('does not consider skip in count queries', (done) => { + Parse.Object.saveAll([ + new TestObject({ skipCount: true }), + new TestObject({ skipCount: true }), + new TestObject({ skipCount: true }) + ]).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('skipCount', true); + return query.count(); + }).then((count) => { + assert.equal(count, 3); + let query = new Parse.Query(TestObject); + query.equalTo('skipCount', true); + query.skip(1); + return query.count(); + }).then((count) => { + assert.equal(count, 3); + let query = new Parse.Query(TestObject); + query.equalTo('skipCount', true); + query.skip(2); + return query.count(); + }).then((count) => { + assert.equal(count, 3); + done(); + }); + }); + + it('can perform count queries', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.greaterThan('number', 1); + query.count().then((count) => { + assert.equal(count, 8); + done(); + }); + }); + + it('can order by ascending numbers', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.ascending('number'); + query.find().then((results) => { + assert.equal(results[0].get('number'), 0); + assert.equal(results[9].get('number'), 9); + done(); + }); + }); + + it('can order by descending numbers', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.descending('number'); + query.find().then((results) => { + assert.equal(results[0].get('number'), 9); + assert.equal(results[9].get('number'), 0); + done(); + }); + }); + + it('can order by asecending number then descending string', (done) => { + Parse.Object.saveAll([ + new TestObject({ doubleOrder: true, number: 3, string: 'a' }), + new TestObject({ doubleOrder: true, number: 1, string: 'b' }), + new TestObject({ doubleOrder: true, number: 3, string: 'c' }), + new TestObject({ doubleOrder: true, number: 2, string: 'd' }), + ]).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('doubleOrder', true); + query.ascending('number').addDescending('string'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 4); + assert.equal(results[0].get('number'), 1); + assert.equal(results[0].get('string'), 'b'); + assert.equal(results[1].get('number'), 2); + assert.equal(results[1].get('string'), 'd'); + assert.equal(results[2].get('number'), 3); + assert.equal(results[2].get('string'), 'c'); + assert.equal(results[3].get('number'), 3); + assert.equal(results[3].get('string'), 'a'); + done(); + }); + }); + + it('can order by descending number then ascending string', (done) => { + Parse.Object.saveAll([ + new TestObject({ otherDoubleOrder: true, number: 3, string: 'a' }), + new TestObject({ otherDoubleOrder: true, number: 1, string: 'b' }), + new TestObject({ otherDoubleOrder: true, number: 3, string: 'c' }), + new TestObject({ otherDoubleOrder: true, number: 2, string: 'd' }), + ]).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('otherDoubleOrder', true); + query.descending('number').addAscending('string'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 4); + assert.equal(results[0].get('number'), 3); + assert.equal(results[0].get('string'), 'a'); + assert.equal(results[1].get('number'), 3); + assert.equal(results[1].get('string'), 'c'); + assert.equal(results[2].get('number'), 2); + assert.equal(results[2].get('string'), 'd'); + assert.equal(results[3].get('number'), 1); + assert.equal(results[3].get('string'), 'b'); + done(); + }); + }); + + it('can order by descending number and string', (done) => { + Parse.Object.saveAll([ + new TestObject({ doubleDescending: true, number: 3, string: 'a' }), + new TestObject({ doubleDescending: true, number: 1, string: 'b' }), + new TestObject({ doubleDescending: true, number: 3, string: 'c' }), + new TestObject({ doubleDescending: true, number: 2, string: 'd' }), + ]).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('doubleDescending', true); + query.descending('number,string'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 4); + assert.equal(results[0].get('number'), 3); + assert.equal(results[0].get('string'), 'c'); + assert.equal(results[1].get('number'), 3); + assert.equal(results[1].get('string'), 'a'); + assert.equal(results[2].get('number'), 2); + assert.equal(results[2].get('string'), 'd'); + assert.equal(results[3].get('number'), 1); + assert.equal(results[3].get('string'), 'b'); + + let query = new Parse.Query(TestObject); + query.equalTo('doubleDescending', true); + query.descending('number, string'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 4); + assert.equal(results[0].get('number'), 3); + assert.equal(results[0].get('string'), 'c'); + assert.equal(results[1].get('number'), 3); + assert.equal(results[1].get('string'), 'a'); + assert.equal(results[2].get('number'), 2); + assert.equal(results[2].get('string'), 'd'); + assert.equal(results[3].get('number'), 1); + assert.equal(results[3].get('string'), 'b'); + + let query = new Parse.Query(TestObject); + query.equalTo('doubleDescending', true); + query.descending(['number', 'string']); + return query.find(); + }).then((results) => { + assert.equal(results.length, 4); + assert.equal(results[0].get('number'), 3); + assert.equal(results[0].get('string'), 'c'); + assert.equal(results[1].get('number'), 3); + assert.equal(results[1].get('string'), 'a'); + assert.equal(results[2].get('number'), 2); + assert.equal(results[2].get('string'), 'd'); + assert.equal(results[3].get('number'), 1); + assert.equal(results[3].get('string'), 'b'); + + let query = new Parse.Query(TestObject); + query.equalTo('doubleDescending', true); + query.descending('number', 'string'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 4); + assert.equal(results[0].get('number'), 3); + assert.equal(results[0].get('string'), 'c'); + assert.equal(results[1].get('number'), 3); + assert.equal(results[1].get('string'), 'a'); + assert.equal(results[2].get('number'), 2); + assert.equal(results[2].get('string'), 'd'); + assert.equal(results[3].get('number'), 1); + assert.equal(results[3].get('string'), 'b'); + + done(); + }); + }); + + it('can not order by password', (done) => { + let query = new Parse.Query('BoxedNumber'); + query.ascending('_password'); + query.find().fail((e) => { + assert.equal(e.code, Parse.Error.INVALID_KEY_NAME); + done(); + }); + }); + + it('can order by _created_at', (done) => { + new Parse.Object({className: 'TestObject', orderedDate: true}).save().then(() => { + return new Parse.Object({className: 'TestObject', orderedDate: true}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', orderedDate: true}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', orderedDate: true}).save(); + }).then(() => { + let query = new Parse.Query('TestObject'); + query.equalTo('orderedDate', true); + query.ascending('_created_at'); + return query.find() + }).then((results) => { + assert(results[0].createdAt < results[1].createdAt); + assert(results[1].createdAt < results[2].createdAt); + assert(results[2].createdAt < results[3].createdAt); + done(); + }); + }); + + it('can order by createdAt', (done) => { + new Parse.Object({className: 'TestObject', orderedDate2: true}).save().then(() => { + return new Parse.Object({className: 'TestObject', orderedDate2: true}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', orderedDate2: true}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', orderedDate2: true}).save(); + }).then(() => { + let query = new Parse.Query('TestObject'); + query.equalTo('orderedDate2', true); + query.descending('createdAt'); + return query.find() + }).then((results) => { + assert(results[0].createdAt > results[1].createdAt); + assert(results[1].createdAt > results[2].createdAt); + assert(results[2].createdAt > results[3].createdAt); + done(); + }); + }); + + it('can order by _updated_at', (done) => { + new Parse.Object({className: 'TestObject', orderedDate3: true}).save().then(() => { + return new Parse.Object({className: 'TestObject', orderedDate3: true}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', orderedDate3: true}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', orderedDate3: true}).save(); + }).then(() => { + let query = new Parse.Query('TestObject'); + query.equalTo('orderedDate3', true); + query.ascending('_updated_at'); + return query.find() + }).then((results) => { + assert(results[0].updatedAt < results[1].updatedAt); + assert(results[1].updatedAt < results[2].updatedAt); + assert(results[2].updatedAt < results[3].updatedAt); + done(); + }); + }); + + it('can order by updatedAt', (done) => { + new Parse.Object({className: 'TestObject', orderedDate4: true}).save().then(() => { + return new Parse.Object({className: 'TestObject', orderedDate4: true}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', orderedDate4: true}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', orderedDate4: true}).save(); + }).then(() => { + let query = new Parse.Query('TestObject'); + query.equalTo('orderedDate4', true); + query.descending('updatedAt'); + return query.find() + }).then((results) => { + assert(results[0].updatedAt > results[1].updatedAt); + assert(results[1].updatedAt > results[2].updatedAt); + assert(results[2].updatedAt > results[3].updatedAt); + done(); + }); + }); + + it('can test time equality', () => { + new Parse.Object({className: 'TestObject', timed: true, name: 'item1'}).save().then(() => { + return new Parse.Object({className: 'TestObject', timed: true, name: 'item2'}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', timed: true, name: 'item3'}).save(); + }).then(() => { + return new Parse.Object({className: 'TestObject', timed: true, name: 'item4'}).save(); + }).then((last) => { + let query = new Parse.Query('TestObject'); + query.equalTo('timed', true); + query.equalTo('createdAt', last.createdAt); + return query.find() + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('name'), 'item4'); + done(); + }); + }); + + it('can test time inequality', () => { + let objects = [ + new Parse.Object({className: 'TestObject', timed2: true, name: 'item1'}), + new Parse.Object({className: 'TestObject', timed2: true, name: 'item2'}), + new Parse.Object({className: 'TestObject', timed2: true, name: 'item3'}), + new Parse.Object({className: 'TestObject', timed2: true, name: 'item4'}) + ]; + + objects[0].save().then(() => { + return objects[1].save(); + }).then(() => { + return objects[2].save(); + }).then(() => { + return objects[2].save(); + }).then(() => { + let query = new Parse.Query('TestObject'); + query.equalTo('timed2', true); + query.lessThan('createdAt', objects[2].createdAt); + query.ascending('createdAt'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 2); + assert.equal(results[0].id, objects[0].id); + assert.equal(results[1].id, objects[1].id); + + let query = new Parse.Query('TestObject'); + query.equalTo('timed2', true); + query.greaterThan('createdAt', objects[2].createdAt); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].id, objects[3].id); + done(); + }); + }); + + it('can test string matching', (done) => { + let obj1 = new TestObject(); + obj1.set('myString', 'football'); + let obj2 = new TestObject(); + obj2.set('myString', 'soccer'); + Parse.Object.saveAll([obj1, obj2]).then(() => { + let query = new Parse.Query(TestObject); + query.matches('myString', '^fo*\\wb[^o]l+$'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('myString'), 'football'); + + let query = new Parse.Query(TestObject); + query.matches('myString', /^fo*\wb[^o]l+$/); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('myString'), 'football'); + done(); + }); + }); + + it('can test case insensitive regex', (done) => { + let obj = new TestObject(); + obj.set('myString', 'hockey'); + obj.save().then(() => { + let query = new Parse.Query(TestObject); + query.matches('myString', 'Hockey', 'i'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('myString'), 'hockey'); + done(); + }); + }); + + it('fails for invalid regex options', (done) => { + let query = new Parse.Query(TestObject); + query.matches('myString', 'football', 'some invalid thing'); + query.find().fail((e) => { + assert.equal(e.code, Parse.Error.INVALID_QUERY); + done(); + }); + }); + + it('can use a regex with all modifiers', (done) => { + let obj = new TestObject(); + obj.set('website', 'PArSe\nCom'); + obj.save().then(() => { + let query = new Parse.Query(TestObject); + query.matches( + 'website', + 'parse # First fragment. We\'ll write this in one case but match ' + + 'insensitively\n.com # Second fragment. This can be separated by any ' + + 'character, including newline', + 'mixs' + ); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('can include regexp modifiers in the constructor', (done) => { + let obj = new TestObject(); + obj.set('website', '\n\nbuffer\n\nparse.COM'); + obj.save().then(() => { + let query = new Parse.Query(TestObject); + query.matches('website', /parse\.com/mi); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + done(); + }); + }); + + it('can test contains', (done) => { + let someAscii = "\\E' !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTU" + + "VWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'"; + Parse.Object.saveAll([ + new TestObject({contains: true, myString: 'zax' + someAscii + 'qub'}), + new TestObject({contains: true, myString: 'start' + someAscii}), + new TestObject({contains: true, myString: someAscii + 'end'}), + new TestObject({contains: true, myString: someAscii}) + ]).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('contains', true); + query.startsWith('myString', someAscii); + return query.find(); + }).then((results) => { + assert.equal(results.length, 2); + let query = new Parse.Query(TestObject); + query.equalTo('contains', true); + query.startsWith('myString', someAscii); + return query.find(); + }).then((results) => { + assert.equal(results.length, 2); + done(); + }); + }); + + it('can test if a key exists', (done) => { + let objects = []; + for (let i = 0; i < 10; i++) { + let item = new TestObject(); + if (i % 2) { + item.set('y', i + 1); + } else { + item.set('z', i + 1); + } + objects.push(item); + } + Parse.Object.saveAll(objects).then(() => { + let query = new Parse.Query(TestObject); + query.exists('y'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 5); + for (let i = 0; i < results.length; i++) { + assert(results[i].has('y')); + } + done(); + }).fail(e => console.log(e)); + }); + + it('can test if a key does not exist', (done) => { + let objects = []; + for (let i = 0; i < 10; i++) { + let item = new TestObject({ dne: true }); + if (i % 2) { + item.set('y', i + 1); + } else { + item.set('z', i + 1); + } + objects.push(item); + } + Parse.Object.saveAll(objects).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('dne', true); + query.doesNotExist('y'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 5); + for (let i = 0; i < results.length; i++) { + assert(results[i].has('z')); + } + done(); + }); + }); + + it('can test if a relation exists', (done) => { + let objects = []; + for (let i = 0; i < 10; i++) { + let container = new Parse.Object('Container', { relation_exists: true }); + if (i % 2) { + container.set('y', i); + } else { + let item = new TestObject(); + item.set('x', i); + container.set('x', item); + objects.push(item); + } + objects.push(container); + } + Parse.Object.saveAll(objects).then(() => { + let query = new Parse.Query('Container'); + query.equalTo('relation_exists', true); + query.exists('x'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 5); + for (let i = 0; i < results.length; i++) { + assert(results[i].has('x')); + } + done(); + }); + }); + + it('can test if a relation does not exist', (done) => { + let objects = []; + for (let i = 0; i < 10; i++) { + let container = new Parse.Object('Container', { relation_dne: true }); + if (i % 2) { + container.set('y', i); + } else { + let item = new TestObject(); + item.set('x', i); + container.set('x', item); + objects.push(item); + } + objects.push(container); + } + Parse.Object.saveAll(objects).then(() => { + let query = new Parse.Query('Container'); + query.equalTo('relation_dne', true); + query.doesNotExist('x'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 5); + for (let i = 0; i < results.length; i++) { + assert(results[i].has('y')); + } + done(); + }); + }); + + it('does not include by default', (done) => { + let child = new TestObject(); + let parent = new Parse.Object('Container'); + child.set('foo', 'bar'); + parent.set('child', child); + Parse.Object.saveAll([child, parent]).then(() => { + let query = new Parse.Query('Container'); + query.equalTo('objectId', parent.id); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + let parentAgain = results[0]; + assert(parentAgain.get('child')); + assert(parentAgain.get('child').id); + assert(!parentAgain.get('child').get('foo')); + done(); + }).fail(e => console.log(e)); + }); + + it('can include nested objects', (done) => { + let child = new TestObject(); + let parent = new Parse.Object('Container'); + child.set('foo', 'bar'); + parent.set('child', child); + Parse.Object.saveAll([child, parent]).then(() => { + let query = new Parse.Query('Container'); + query.equalTo('objectId', parent.id); + query.include('child'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + let parentAgain = results[0]; + assert(parentAgain.get('child')); + assert(parentAgain.get('child').id); + assert.equal(parentAgain.get('child').get('foo'), 'bar'); + done(); + }); + }); + + it('can include nested objects via array', (done) => { + let child = new TestObject(); + let parent = new Parse.Object('Container'); + child.set('foo', 'bar'); + parent.set('child', child); + Parse.Object.saveAll([child, parent]).then(() => { + let query = new Parse.Query('Container'); + query.equalTo('objectId', parent.id); + query.include(['child']); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + let parentAgain = results[0]; + assert(parentAgain.get('child')); + assert(parentAgain.get('child').id); + assert.equal(parentAgain.get('child').get('foo'), 'bar'); + done(); + }); + }); + + it('can do a nested include', (done) => { + let Child = Parse.Object.extend('Child'); + let Parent = Parse.Object.extend('Parent'); + let Grandparent = Parse.Object.extend('Grandparent'); + + let objects = []; + for (let i = 0; i < 5; i++) { + let grandparent = new Grandparent({ + nested: true, + z: i, + parent: new Parent({ + y: i, + child: new Child({ + x: i + }), + }), + }); + + objects.push(grandparent); + } + + Parse.Object.saveAll(objects).then(() => { + let q = new Parse.Query('Grandparent'); + q.equalTo('nested', true); + q.include('parent.child'); + return q.find(); + }).then((results) => { + assert.equal(results.length, 5); + results.forEach((o) => { + assert.equal(o.get('z'), o.get('parent').get('y')); + assert.equal(o.get('z'), o.get('parent').get('child').get('x')); + }); + done(); + }); + }); + + it('can include without changing dirty', (done) => { + let parent = new Parse.Object('ParentObject'); + let child = new Parse.Object('ChildObject'); + parent.set('child', child); + child.set('foo', 'bar'); + + Parse.Object.saveAll([child, parent]).then(() => { + let query = new Parse.Query('ParentObject'); + query.include('child'); + query.equalTo('objectId', parent.id); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + let parentAgain = results[0]; + let childAgain = parentAgain.get('child'); + assert.equal(child.id, childAgain.id); + assert.equal(parent.id, parentAgain.id); + assert.equal(childAgain.get('foo'), 'bar'); + assert(!parentAgain.dirty()); + assert(!childAgain.dirty()); + done(); + }); + }); + + it('uses subclasses when creating objects', (done) => { + let ParentObject = Parse.Object.extend({ className: 'ParentObject' }); + let ChildObject = Parse.Object.extend('ChildObject', { + foo() { + return 'foo'; + } + }); + + let parent = new ParentObject(); + let child = new ChildObject(); + parent.set('child', child); + Parse.Object.saveAll([child, parent]).then(() => { + ChildObject = Parse.Object.extend('ChildObject', { + bar() { + return 'bar'; + } + }); + + let query = new Parse.Query(ParentObject); + query.equalTo('objectId', parent.id); + query.include('child'); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + let parentAgain = results[0]; + let childAgain = parentAgain.get('child'); + assert.equal(childAgain.foo(), 'foo'); + assert.equal(childAgain.bar(), 'bar'); + done(); + }); + }); + + it('can match the results of another query', (done) => { + let ParentObject = Parse.Object.extend('ParentObject'); + let ChildObject = Parse.Object.extend('ChildObject'); + let objects = []; + for (let i = 0; i < 10; i++) { + objects.push(new ParentObject({ + child: new ChildObject({x: i, qtest: true}), + x: 10 + i, + })); + } + Parse.Object.saveAll(objects).then(() => { + let subQuery = new Parse.Query(ChildObject); + subQuery.equalTo('qtest', true); + subQuery.greaterThan('x', 5); + let q = new Parse.Query(ParentObject); + q.matchesQuery('child', subQuery); + return q.find(); + }).then((results) => { + assert.equal(results.length, 4); + results.forEach((o) => { + assert(o.get('x') > 15); + }); + done(); + }); + }); + + it('can not match the results of another query', (done) => { + let ParentObject = Parse.Object.extend('ParentObject'); + let ChildObject = Parse.Object.extend('ChildObject'); + let objects = []; + for (let i = 0; i < 10; i++) { + objects.push(new ParentObject({ + child: new ChildObject({x: i, dneqtest: true}), + dneqtest: true, + x: 10 + i, + })); + } + Parse.Object.saveAll(objects).then(() => { + let subQuery = new Parse.Query(ChildObject); + subQuery.equalTo('dneqtest', true); + subQuery.greaterThan('x', 5); + let q = new Parse.Query(ParentObject); + q.equalTo('dneqtest', true); + q.doesNotMatchQuery('child', subQuery); + return q.find(); + }).then((results) => { + assert.equal(results.length, 6); + results.forEach((o) => { + assert(o.get('x') >= 10); + assert(o.get('x') <= 15); + }); + done(); + }); + }); + + it('can select keys from a matched query', (done) => { + let Restaurant = Parse.Object.extend('Restaurant'); + let Person = Parse.Object.extend('Person'); + let objects = [ + new Restaurant({ rating: 5, location: 'Djibouti' }), + new Restaurant({ rating: 3, location: 'Ouagadougou' }), + new Person({ name: 'Bob', hometown: 'Djibouti' }), + new Person({ name: 'Tom', hometown: 'Ouagadougou' }), + new Person({ name: 'Billy', hometown: 'Detroit' }), + ]; + + Parse.Object.saveAll(objects).then(() => { + let query = new Parse.Query(Restaurant); + query.greaterThan('rating', 4); + let mainQuery = new Parse.Query(Person); + mainQuery.matchesKeyInQuery('hometown', 'location', query); + return mainQuery.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('name'), 'Bob'); + + let query = new Parse.Query(Restaurant); + query.greaterThan('rating', 4); + let mainQuery = new Parse.Query(Person); + mainQuery.doesNotMatchKeyInQuery('hometown', 'location', query); + mainQuery.ascending('name'); + return mainQuery.find(); + }).then((results) => { + assert.equal(results.length, 2); + assert.equal(results[0].get('name'), 'Billy'); + assert.equal(results[1].get('name'), 'Tom'); + + done(); + }); + }); + + it('supports objects with length', (done) => { + let obj = new TestObject(); + obj.set('length', 5); + assert.equal(obj.get('length'), 5); + obj.save().then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('objectId', obj.id); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('length'), 5); + done(); + }); + }); + + it('can include User fields', (done) => { + Parse.User.signUp('bob', 'password', { age: 21 }).then((user) => { + let obj = new TestObject(); + return obj.save({ owner: user }); + }).then((obj) => { + let query = new Parse.Query(TestObject); + query.include('owner'); + return query.get(obj.id); + }).then((objAgain) => { + assert(objAgain.get('owner') instanceof Parse.User); + assert.equal(objAgain.get('owner').get('age'), 21); + done(); + }); + }); + + it('can build OR queries', (done) => { + let objects = []; + for (let i = 0; i < 10; i++) { + let obj = new Parse.Object('BoxedNumber'); + obj.set({ x: i, orquery: true }); + objects.push(obj); + } + Parse.Object.saveAll(objects).then(() => { + let q1 = new Parse.Query('BoxedNumber'); + q1.equalTo('orquery', true); + q1.lessThan('x', 2); + let q2 = new Parse.Query('BoxedNumber'); + q2.equalTo('orquery', true); + q2.greaterThan('x', 5); + let orQuery = Parse.Query.or(q1, q2); + return orQuery.find(); + }).then((results) => { + assert.equal(results.length, 6); + results.forEach((number) => { + assert(number.get('x') < 2 || number.get('x') > 5); + }); + done(); + }); + }); + + it('can build complex OR queries', (done) => { + let objects = []; + for (let i = 0; i < 10; i++) { + let child = new Parse.Object('Child'); + child.set('x', i); + child.set('complexor', true); + let parent = new Parse.Object('Parent'); + parent.set('child', child); + parent.set('complexor', true); + parent.set('y', i); + objects.push(parent); + } + Parse.Object.saveAll(objects).then(() => { + let subQuery = new Parse.Query('Child'); + subQuery.equalTo('x', 4); + subQuery.equalTo('complexor', true); + let q1 = new Parse.Query('Parent'); + q1.matchesQuery('child', subQuery); + let q2 = new Parse.Query('Parent'); + q2.equalTo('complexor', true); + q2.lessThan('y', 2); + let orQuery = new Parse.Query.or(q1, q2); + return orQuery.find(); + }).then((results) => { + assert.equal(results.length, 3); + done(); + }); + }); + + it('can iterate over results with each', (done) => { + let items = []; + for (let i = 0; i < 50; i++) { + items.push(new TestObject({ x: i, eachtest: true })); + } + let seen = []; + Parse.Object.saveAll(items).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('eachtest', true); + query.lessThan('x', 25); + + return query.each((obj) => { + seen[obj.get('x')] = (seen[obj.get('x')] || 0) + 1; + }); + }).then(() => { + assert.equal(seen.length, 25); + for (let i = 0; i < seen.length; i++) { + assert.equal(seen[i], 1); + } + done(); + }); + }); + + it('fails query.each with order', (done) => { + let items = []; + for (let i = 0; i < 50; i++) { + items.push(new TestObject({ x: i, eachtest: true })); + } + let seen = []; + Parse.Object.saveAll(items).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('eachtest', true); + query.lessThan('x', 25); + query.ascending('x'); + + return query.each((obj) => { + seen[obj.get('x')] = (seen[obj.get('x')] || 0) + 1; + }); + }).then(null, () => { + done(); + }); + }); + + it('fails query.each with limit', (done) => { + let items = []; + for (let i = 0; i < 50; i++) { + items.push(new TestObject({ x: i, eachtest: true })); + } + let seen = []; + Parse.Object.saveAll(items).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('eachtest', true); + query.lessThan('x', 25); + query.limit(20); + + return query.each((obj) => { + seen[obj.get('x')] = (seen[obj.get('x')] || 0) + 1; + }); + }).then(null, () => { + done(); + }); + }); + + it('fails query.each with skip', (done) => { + let items = []; + for (let i = 0; i < 50; i++) { + items.push(new TestObject({ x: i, eachtest: true })); + } + let seen = []; + Parse.Object.saveAll(items).then(() => { + let query = new Parse.Query(TestObject); + query.equalTo('eachtest', true); + query.lessThan('x', 25); + query.skip(20); + + return query.each((obj) => { + seen[obj.get('x')] = (seen[obj.get('x')] || 0) + 1; + }); + }).then(null, () => { + done(); + }); + }); + + it('can select specific keys', (done) => { + let obj = new TestObject({ foo: 'baz', bar: 1 }); + obj.save().then(() => { + let q = new Parse.Query(TestObject); + q.equalTo('objectId', obj.id); + q.select('foo'); + return q.first(); + }).then((result) => { + assert(result.id); + assert(result.createdAt); + assert(result.updatedAt); + assert(!result.dirty()); + assert.equal(result.get('foo'), 'baz'); + assert.equal(result.get('bar'), undefined); + done(); + }); + }); + + it('can select specific keys with each', (done) => { + let obj = new TestObject({ foo: 'baz', bar: 1 }); + obj.save().then(() => { + let q = new Parse.Query(TestObject); + q.equalTo('objectId', obj.id); + q.select('foo'); + return q.each((o) => { + assert(o.id); + assert.equal(o.get('foo'), 'baz'); + assert.equal(o.get('bar'), undefined); + }); + }).then(() => { + done(); + }); + }); +}); diff --git a/integration/test/ParseRelationTest.js b/integration/test/ParseRelationTest.js new file mode 100644 index 000000000..d0373af96 --- /dev/null +++ b/integration/test/ParseRelationTest.js @@ -0,0 +1,217 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); + +describe('Parse Relation', () => { + before((done) => { + Parse.initialize('integration', null, 'notsosecret'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear().then(() => { + done(); + }); + }); + + it('can do a simple add and remove', (done) => { + let ChildObject = Parse.Object.extend('ChildObject'); + let childObjects = []; + for (let i = 0; i < 10; i++) { + childObjects.push(new ChildObject({x: i})); + } + let rel = null; + let parent = null; + Parse.Object.saveAll(childObjects).then(() => { + parent = new Parse.Object('ParentObject'); + parent.set('x', 4); + rel = parent.relation('child'); + rel.add(childObjects[0]); + return parent.save(); + }).then((parentAgain) => { + assert(!parent.dirty('child')); + assert(!parentAgain.dirty('child')); + return rel.query().find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].id, childObjects[0].id); + + rel.remove(childObjects[0]); + return parent.save(); + }).then((parentAgain) => { + assert(!parent.dirty('child')); + assert(!parentAgain.dirty('child')); + return rel.query().find(); + }).then((results) => { + assert.equal(results.length, 0); + done(); + }); + }); + + it('can query relation without schema', (done) => { + let ChildObject = Parse.Object.extend('ChildObject'); + let childObjects = []; + for (let i = 0; i < 10; i++) { + childObjects.push(new ChildObject({x: i})); + } + let parent = null; + Parse.Object.saveAll(childObjects).then(() => { + parent = new Parse.Object('ParentObject'); + parent.set('x', 4); + let rel = parent.relation('child'); + rel.add(childObjects[0]); + return parent.save(); + }).then((parentAgain) => { + assert(!parent.dirty('child')); + assert(!parentAgain.dirty('child')); + return parentAgain.relation('child').query().find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].id, childObjects[0].id); + done(); + }); + }); + + it('can compound add and remove operations', (done) => { + let ChildObject = Parse.Object.extend('ChildObject'); + let childObjects = []; + for (let i = 0; i < 10; i++) { + childObjects.push(new ChildObject({x: i})); + } + let parent = null; + let rel = null; + Parse.Object.saveAll(childObjects).then(() => { + parent = new Parse.Object('ParentObject'); + parent.set('x', 4); + rel = parent.relation('child'); + rel.add(childObjects[0]); + rel.add(childObjects[1]); + rel.remove(childObjects[0]); + rel.add(childObjects[2]); + return parent.save(); + }).then((parentAgain) => { + return rel.query().find(); + }).then((results) => { + assert.equal(results.length, 2); + + rel.remove(childObjects[1]); + rel.remove(childObjects[2]); + rel.add(childObjects[1]); + rel.add(childObjects[0]); + + return parent.save(); + }).then(() => { + return rel.query().find(); + }).then((results) => { + assert.equal(results.length, 2); + done(); + }); + }); + + it('can refine relation queries', (done) => { + let ChildObject = Parse.Object.extend('ChildObject'); + let childObjects = []; + for (let i = 0; i < 10; i++) { + childObjects.push(new ChildObject({x: i})); + } + Parse.Object.saveAll(childObjects).then(() => { + let parent = new Parse.Object('ParentObject'); + parent.set('x', 4); + let rel = parent.relation('child'); + rel.add(childObjects[0]); + rel.add(childObjects[1]); + rel.add(childObjects[2]); + return parent.save(); + }).then((parentAgain) => { + let q = parentAgain.relation('child').query(); + q.equalTo('x', 2); + return q.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].id, childObjects[2].id); + done(); + }); + }); + + it('can query relation fields', (done) => { + let ChildObject = Parse.Object.extend('ChildObject'); + let childObjects = []; + for (let i = 0; i < 10; i++) { + childObjects.push(new ChildObject({x: i})); + } + Parse.Object.saveAll(childObjects).then(() => { + let parent = new Parse.Object('ParentObject'); + parent.set('x', 4); + let rel = parent.relation('child'); + rel.add(childObjects[0]); + rel.add(childObjects[1]); + rel.add(childObjects[2]); + let parent2 = new Parse.Object('ParentObject'); + parent2.set('x', 3); + let rel2 = parent2.relation('child'); + rel2.add(childObjects[4]); + rel2.add(childObjects[5]); + rel2.add(childObjects[6]); + return Parse.Object.saveAll([parent, parent2]); + }).then((parents) => { + let q = new Parse.Query('ParentObject'); + let objects = [childObjects[4], childObjects[9]]; + q.containedIn('child', objects); + return q.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert.equal(results[0].get('x'), 3); + done(); + }); + }); + + it('can get a query on a relation when the parent is unfetched', (done) => { + let Wheel = Parse.Object.extend('Wheel'); + let Car = Parse.Object.extend('Car'); + let origWheel = new Wheel(); + origWheel.save().then(() => { + let car = new Car(); + let relation = car.relation('wheels'); + relation.add(origWheel); + return car.save(); + }).then((car) => { + let unfetchedCar = new Car(); + unfetchedCar.id = car.id; + let relation = unfetchedCar.relation('wheels'); + let query = relation.query(); + + return query.get(origWheel.id); + }).then((wheel) => { + assert.equal(wheel.className, 'Wheel'); + assert.equal(wheel.id, origWheel.id); + done(); + }); + }); + + it('can find a query on a relation when the parent is unfetched', (done) => { + let Wheel = Parse.Object.extend('Wheel'); + let Car = Parse.Object.extend('Car'); + let origWheel = new Wheel(); + origWheel.save().then(() => { + let car = new Car(); + let relation = car.relation('wheels'); + relation.add(origWheel); + return car.save(); + }).then((car) => { + let unfetchedCar = new Car(); + unfetchedCar.id = car.id; + let relation = unfetchedCar.relation('wheels'); + let query = relation.query(); + + return query.find(); + }).then((wheels) => { + assert.equal(wheels.length, 1); + assert.equal(wheels[0].className, 'Wheel'); + assert.equal(wheels[0].id, origWheel.id); + done(); + }); + }); +}); diff --git a/integration/test/ParseRoleTest.js b/integration/test/ParseRoleTest.js new file mode 100644 index 000000000..72e882bc1 --- /dev/null +++ b/integration/test/ParseRoleTest.js @@ -0,0 +1,33 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); + +function privateTo(someone) { + let acl = new Parse.ACL(); + acl.setReadAccess(someone, true); + acl.setWriteAccess(someone, true); + return acl; +} + +function publicAccess() { + let acl = new Parse.ACL(); + acl.setPublicReadAccess(true); + acl.setPublicWriteAccess(true); + return acl; +} + +function createUser(username) { + let user = new Parse.User(); + user.set('username', username); + user.set('password', username); + return user; +} + +describe('Parse Role', () => { + /** TODO: Implement these. There was some bugginess related to parse-server. **/ +}); diff --git a/integration/test/ParseSubclassTest.js b/integration/test/ParseSubclassTest.js new file mode 100644 index 000000000..42895ed22 --- /dev/null +++ b/integration/test/ParseSubclassTest.js @@ -0,0 +1,187 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +describe('Parse Object Subclasses', () => { + before((done) => { + Parse.initialize('integration', null, 'notsosecret'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear().then(() => { + done(); + }); + }); + + it('uses subclasses when doing query find', (done) => { + let Subclass = Parse.Object.extend('Subclass', { + initialize(attributes, options, number) { + this.number = number || -1; + } + }); + + let object = new Subclass({}, {}, 57); + assert.equal(object.number, 57); + object.save().then(() => { + let query = new Parse.Query(Subclass); + return query.find(); + }).then((results) => { + assert.equal(results.length, 1); + assert(results[0] instanceof Subclass); + assert.equal(results[0].number, -1); + done(); + }); + }); + + it('uses subclasses when doing query get', (done) => { + let Subclass = Parse.Object.extend('Subclass', { + initialize(attributes, options, number) { + this.number = number || -1; + } + }); + + let object = new Subclass({}, {}, 57); + assert.equal(object.number, 57); + object.save().then(() => { + let query = new Parse.Query(Subclass); + return query.get(object.id); + }).then((result) => { + assert(result instanceof Subclass); + assert.equal(result.number, -1); + done(); + }); + }); + + it('uses subclasses with array results', (done) => { + let Container = Parse.Object.extend('Container'); + let Item = Parse.Object.extend('Item'); + let ItemChild = Parse.Object.extend('Item'); + let ItemGrandchild = ItemChild.extend(); + + let item = new Item(); + item.save({ foo: 'bar' }).then(() => { + let container = new Container(); + return container.save({ items: [item] }); + }).then((container) => { + let query = new Parse.Query(Container); + return query.get(container.id); + }).then((container) => { + assert(container instanceof Container); + assert.equal(container.get('items').length, 1); + let item = container.get('items')[0]; + assert(item instanceof Item); + assert(item instanceof ItemChild); + assert(item instanceof ItemGrandchild); + done(); + }); + }); + + it('can subclass multiple levels explicitly', (done) => { + let Parent = Parse.Object.extend('MyClass', { + initialize() { + Parent.__super__.initialize.apply(this, arguments); + this.parent = true; + } + }); + + let Child = Parent.extend({ + initialize() { + Child.__super__.initialize.apply(this, arguments); + this.child = true; + } + }); + + let Grandchild = Child.extend({ + initialize() { + Grandchild.__super__.initialize.apply(this, arguments); + this.grandchild = true; + } + }); + + let object = new Parent(); + + object.save().then(() => { + let query = new Parse.Query(Grandchild); + return query.get(object.id); + }).then((result) => { + assert(result instanceof Parent); + assert(result instanceof Child); + assert(result instanceof Grandchild); + assert(result.parent); + assert(result.child); + assert(result.grandchild); + done(); + }); + }); + + it('can subclass multiple levels implicitly', (done) => { + let Parent = Parse.Object.extend('MyClass', { + initialize() { + Parent.__super__.initialize.apply(this, arguments); + this.parent = true; + } + }); + + let Child = Parse.Object.extend('MyClass', { + initialize() { + Child.__super__.initialize.apply(this, arguments); + this.child = true; + } + }); + + let Grandchild = Parse.Object.extend('MyClass', { + initialize() { + Grandchild.__super__.initialize.apply(this, arguments); + this.grandchild = true; + } + }); + + let object = new Parent(); + + object.save().then(() => { + let query = new Parse.Query(Grandchild); + return query.get(object.id); + }).then((result) => { + assert(result instanceof Parent); + assert(result instanceof Child); + assert(result instanceof Grandchild); + assert(result.parent); + assert(result.child); + assert(result.grandchild); + done(); + }); + }); + + it('can subclass multiple levels explicitly with different names', (done) => { + let Parent = Parse.Object.extend('MyClass'); + let Child = Parent.extend(); + let Grandchild = Child.extend('NewClass'); + + let object = new Parent(); + + object.save().then(() => { + let query = new Parse.Query(Child); + return query.get(object.id); + }).then((result) => { + assert(result instanceof Parent); + assert(result instanceof Child); + + let query = new Parse.Query(Grandchild); + return query.get(object.id); + }).then(null, () => { + // No object found + done(); + }); + }); + + it('propagates instance properties', () => { + let Squirtle = Parse.Object.extend('Squirtle', { + water: true + }); + var Wartortle = Squirtle.extend('Wartortle'); + let wartortle = new Wartortle(); + assert(wartortle.water); + }); +}); diff --git a/integration/test/ParseUserTest.js b/integration/test/ParseUserTest.js new file mode 100644 index 000000000..6ebcb5af2 --- /dev/null +++ b/integration/test/ParseUserTest.js @@ -0,0 +1,376 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const mocha = require('mocha'); +const Parse = require('../../node'); + +const TestObject = Parse.Object.extend('TestObject'); + +describe('Parse User', () => { + before(() => { + Parse.initialize('integration', null, 'notsosecret'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + }); + + beforeEach((done) => { + let promise = Parse.Promise.as(); + try { + promise = Parse.User.logOut(); + } catch (e) {} + promise.then(() => { + return clear(); + }).then(() => { + done(); + }); + }); + + it('can sign up users via static method', (done) => { + Parse.User.signUp('asdf', 'zxcv').then((user) => { + assert(user.getSessionToken()); + done(); + }); + }); + + it('can sign up via instance method', (done) => { + let user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('zxcv'); + user.signUp().then((user) => { + assert(user.getSessionToken()); + done(); + }); + }); + + it('fails log in with wrong username', (done) => { + Parse.User.signUp('asdf', 'zxcv').then(() => { + return Parse.User.logIn('false_user', 'asdf3'); + }).then(null, () => { + done(); + }); + }); + + it('fails log in with wrong password', (done) => { + Parse.User.signUp('asdf', 'zxcv').then(() => { + return Parse.User.logIn('asdf', 'asdfWrong'); + }).then(null, () => { + done(); + }); + }); + + it('can log in a user', (done) => { + Parse.User.signUp('asdf', 'zxcv').then(() => { + return Parse.User.logIn('asdf', 'zxcv'); + }).then((user) => { + assert.equal(user.get('username'), 'asdf'); + done(); + }); + }); + + it('can become a user', (done) => { + Parse.User.enableUnsafeCurrentUser(); + let user = null; + let session = null; + Parse.User.signUp('jason', 'parse', {'code': 'red'}).then((newUser) => { + assert.equal(Parse.User.current(), newUser); + user = newUser; + session = newUser.getSessionToken(); + assert(session); + + return Parse.User.logOut(); + }).then(() => { + assert(!Parse.User.current()); + + return Parse.User.become(sessionToken); + }).then((user) => { + assert.equal(Parse.User.current(), user); + assert(user); + assert.equal(user.id, newUser.id) + assert.equal(user.get('code'), 'red'); + + return Parse.User.logOut(); + }).then(() => { + assert(!Parse.User.current()); + + return Parse.User.become('garbage'); + }).then(null, () => { + done(); + }); + }); + + it('cannot save non-authed user', (done) => { + let user = new Parse.User(); + let notAuthed = null; + user.set({ + password: 'asdf', + email: 'asdf@example.com', + username: 'zxcv', + }); + user.signUp().then((userAgain) => { + assert.equal(user, userAgain); + let query = new Parse.Query(Parse.User); + return query.get(user.id); + }).then((userNotAuthed) => { + notAuthed = userNotAuthed; + user = new Parse.User(); + user.set({ + username: 'hacker', + password: 'password', + }); + return user.signUp(); + }).then((userAgain) => { + assert.equal(userAgain, user); + notAuthed.set('username', 'changed'); + return notAuthed.save(); + }).then(null, (e) => { + assert.equal(e.code, Parse.Error.SESSION_MISSING); + done(); + }); + }); + + it('cannot delete non-authed user', (done) => { + let user = new Parse.User(); + let notAuthed = null; + user.signUp({ + password: 'asdf', + email: 'asdf@example.com', + username: 'zxcv', + }).then(() => { + let query = new Parse.Query(Parse.User); + return query.get(user.id); + }).then((userNotAuthed) => { + notAuthed = userNotAuthed; + user = new Parse.User(); + return user.signUp({ + username: 'hacker', + password: 'password', + }); + }).then((userAgain) => { + assert.equal(userAgain, user); + notAuthed.set('username', 'changed'); + return notAuthed.destroy(); + }).then(null, (e) => { + assert.equal(e.code, Parse.Error.SESSION_MISSING); + done(); + }); + }); + + it('cannot saveAll with non-authed user', (done) => { + let user = new Parse.User(); + let notAuthed = null; + user.signUp({ + password: 'asdf', + email: 'asdf@example.com', + username: 'zxcv', + }).then(() => { + let query = new Parse.Query(Parse.User); + return query.get(user.id); + }).then((userNotAuthed) => { + notAuthed = userNotAuthed; + user = new Parse.User(); + return user.signUp({ + username: 'hacker', + password: 'password', + }); + }).then(() => { + let query = new Parse.Query(Parse.User); + return query.get(user.id); + }).then((userNotAuthedNotChanged) => { + notAuthed.set('username', 'changed'); + let object = new TestObject(); + return object.save({ user: userNotAuthedNotChanged }); + }).then((o) => { + let item1 = new TestObject(); + return item1.save({ number: 0 }); + }).then((item1) => { + item1.set('number', 1); + let item2 = new TestObject(); + item2.set('number', 2); + return Parse.Object.saveAll([item1, item2, notAuthed]); + }).then(null, (e) => { + assert.equal(e.code, Parse.Error.SESSION_MISSING); + done(); + }); + }); + + it('can store the current user', (done) => { + Parse.User.enableUnsafeCurrentUser(); + let user = new Parse.User(); + user.set('password', 'asdf'); + user.set('email', 'asdf@example.com'); + user.set('username', 'zxcv'); + user.signUp().then(() => { + let current = Parse.User.current(); + assert.equal(user.id, current.id); + assert(user.getSessionToken()); + + let currentAgain = Parse.User.current(); + assert.equal(current, currentAgain); + + return Parse.User.logOut(); + }).then(() => { + assert.equal(Parse.User.current(), null); + done(); + }); + }); + + it('can test if a user is current', (done) => { + Parse.User.enableUnsafeCurrentUser(); + let user1 = new Parse.User(); + let user2 = new Parse.User(); + let user3 = new Parse.User(); + + user1.set('username', 'a'); + user2.set('username', 'b'); + user3.set('username', 'c'); + + user1.set('password', 'password'); + user2.set('password', 'password'); + user3.set('password', 'password'); + + user1.signUp().then(() => { + assert(user1.isCurrent()); + assert(!user2.isCurrent()); + assert(!user3.isCurrent()); + + return user2.signUp(); + }).then(() => { + assert(!user1.isCurrent()); + assert(user2.isCurrent()); + assert(!user3.isCurrent()); + + return user3.signUp(); + }).then(() => { + assert(!user1.isCurrent()); + assert(!user2.isCurrent()); + assert(user3.isCurrent()); + + return Parse.User.logIn('a', 'password'); + }).then(() => { + assert(user1.isCurrent()); + assert(!user2.isCurrent()); + assert(!user3.isCurrent()); + + return Parse.User.logIn('b', 'password'); + }).then(() => { + assert(!user1.isCurrent()); + assert(user2.isCurrent()); + assert(!user3.isCurrent()); + + return Parse.User.logIn('c', 'password'); + }).then(() => { + assert(!user1.isCurrent()); + assert(!user2.isCurrent()); + assert(user3.isCurrent()); + + return Parse.User.logOut(); + }).then(() => { + assert(!user3.isCurrent()); + done(); + }); + }); + + it('can query for users', (done) => { + let user = new Parse.User(); + user.set('password', 'asdf'); + user.set('email', 'asdf@exxample.com'); + user.set('username', 'zxcv'); + user.signUp().then(() => { + let query = new Parse.Query(Parse.User); + return query.get(user.id); + }).then((u) => { + assert.equal(u.id, user.id); + return new Parse.Query(Parse.User).find(); + }).then((users) => { + assert.equal(users.length, 1); + assert.equal(users[0].id, user.id); + done(); + }); + }); + + it('does not log in a user when saving', (done) => { + Parse.User.enableUnsafeCurrentUser(); + let user = new Parse.User(); + user.save({ + password: 'asdf', + email: 'asdf@example.com', + username: 'zxcv', + }).then(() => { + assert(!Parse.User.current()); + done(); + }); + }); + + it('can update users', (done) => { + let user = new Parse.User(); + user.signUp({ + password: 'asdf', + email: 'asdf@example.com', + username: 'zxcv', + }).then(() => { + user.set('username', 'test'); + return user.save(); + }).then(() => { + assert.equal(Object.keys(user.attributes).length, 6); + assert(user.attributes.hasOwnProperty('username')); + assert(user.attributes.hasOwnProperty('email')); + return user.destroy(); + }).then(() => { + let query = new Parse.Query(Parse.User); + return query.get(user.id); + }).then(null, (e) => { + assert.equal(e.code, Parse.Error.OBJECT_NOT_FOUND); + done(); + }); + }); + + it('can count users', (done) => { + let james = new Parse.User(); + james.set('username', 'james'); + james.set('password', 'mypass'); + james.signUp().then(() => { + let kevin = new Parse.User(); + kevin.set('username', 'kevin'); + kevin.set('password', 'mypass'); + return kevin.signUp(); + }).then(() => { + let query = new Parse.Query(Parse.User); + return query.count(); + }).then((c) => { + assert.equal(c, 2); + done(); + }); + }); + + it('can sign up user with container class', (done) => { + Parse.User.signUp('ilya', 'mypass', { 'array': ['hello'] }).then(() => { + done(); + }); + }); + + it('handles user subclassing', (done) => { + let SuperUser = new Parse.Object.extend('User'); + let user = new SuperUser(); + user.set('username', 'bob'); + user.set('password', 'welcome'); + assert(user instanceof Parse.User); + user.signUp().then(() => { + done(); + }); + }); + + it('uses subclasses when doing signup', (done) => { + let SuperUser = Parse.User.extend({ + secret() { + return 1337; + } + }); + + Parse.User.signUp('bob', 'welcome').then((user) => { + assert(user instanceof SuperUser); + assert.equal(user.secret(), 1337); + done(); + }); + }); +}); \ No newline at end of file diff --git a/integration/test/clear.js b/integration/test/clear.js new file mode 100644 index 000000000..8f5970c75 --- /dev/null +++ b/integration/test/clear.js @@ -0,0 +1,5 @@ +const Parse = require('parse/node'); + +module.exports = function() { + return Parse._ajax('GET', 'http://localhost:1337/clear', ''); +}; diff --git a/run_integration.sh b/run_integration.sh new file mode 100755 index 000000000..2ff1a4a4f --- /dev/null +++ b/run_integration.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +npm run build +cd integration +npm install +node server.js & +PID=$! +npm test +C=$? +kill -9 $PID +exit $C \ No newline at end of file