From 5974c6afdf73ecc4604ffcd686332c3f3d3d0838 Mon Sep 17 00:00:00 2001 From: crandmck Date: Fri, 8 Jan 2016 17:10:44 -0800 Subject: [PATCH 01/35] Pull in API doc fix from PR into master #1910 --- lib/persisted-model.js | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/persisted-model.js b/lib/persisted-model.js index b3bb62281..ab17ef16c 100644 --- a/lib/persisted-model.js +++ b/lib/persisted-model.js @@ -111,13 +111,29 @@ module.exports = function(registry) { }; /** - * Find one record matching the optional `where` filter. The same as `find`, but limited to one object. - * Returns an object, not collection. - * If not found, create the object using data provided as second argument. + * Finds one record matching the optional filter object. If not found, creates + * the object using the data provided as second argument. In this sense it is + * the same as `find`, but limited to one object. Returns an object, not + * collection. If you don't provide the filter object argument, it tries to + * locate an existing object that matches the `data` argument. * - * @param {Object} where Where clause, such as `{test: 'me'}` - *
see - * [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforothermethods). + * @options {Object} [filter] Optional Filter object; see below. + * @property {String|Object|Array} fields Identify fields to include in return result. + *
See [Fields filter](http://docs.strongloop.com/display/LB/Fields+filter). + * @property {String|Object|Array} include See PersistedModel.include documentation. + *
See [Include filter](http://docs.strongloop.com/display/LB/Include+filter). + * @property {Number} limit Maximum number of instances to return. + *
See [Limit filter](http://docs.strongloop.com/display/LB/Limit+filter). + * @property {String} order Sort order: either "ASC" for ascending or "DESC" for descending. + *
See [Order filter](http://docs.strongloop.com/display/LB/Order+filter). + * @property {Number} skip Number of results to skip. + *
See [Skip filter](http://docs.strongloop.com/display/LB/Skip+filter). + * @property {Object} where Where clause, like + * ``` + * {where: {key: val, key2: {gt: val2}, ...}} + * ``` + *
See + * [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforqueries). * @param {Object} data Data to insert if object matching the `where` filter is not found. * @callback {Function} callback Callback function called with `cb(err, instance)` arguments. Required. * @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object). From 8deec2e89aeea65dfbd8a9f43e2dc15a11faef15 Mon Sep 17 00:00:00 2001 From: Amir Jafarian Date: Thu, 17 Dec 2015 12:02:35 -0500 Subject: [PATCH 02/35] Checkpoint speedup --- common/models/checkpoint.js | 64 ++++++++++++++++---------------- lib/persisted-model.js | 7 +--- test/checkpoint.test.js | 74 ++++++++++++++++++++++++++++++++----- 3 files changed, 99 insertions(+), 46 deletions(-) diff --git a/common/models/checkpoint.js b/common/models/checkpoint.js index 304f83148..59cc33de0 100644 --- a/common/models/checkpoint.js +++ b/common/models/checkpoint.js @@ -27,43 +27,45 @@ module.exports = function(Checkpoint) { * Get the current checkpoint id * @callback {Function} callback * @param {Error} err - * @param {Number} checkpointId The current checkpoint id + * @param {Number} checkpoint The current checkpoint seq */ - Checkpoint.current = function(cb) { var Checkpoint = this; - this.find({ - limit: 1, - order: 'seq DESC' - }, function(err, checkpoints) { - if (err) return cb(err); - var checkpoint = checkpoints[0]; - if (checkpoint) { - cb(null, checkpoint.seq); - } else { - Checkpoint.create({ seq: 1 }, function(err, checkpoint) { - if (err) return cb(err); - cb(null, checkpoint.seq); - }); - } + Checkpoint._getSingleton(function(err, cp) { + cb(err, cp.seq); }); }; - Checkpoint.observe('before save', function(ctx, next) { - if (!ctx.instance) { - // Example: Checkpoint.updateAll() and Checkpoint.updateOrCreate() - return next(new Error('Checkpoint does not support partial updates.')); - } + Checkpoint._getSingleton = function(cb) { + var query = {limit: 1}; // match all instances, return only one + var initialData = {seq: 1}; + this.findOrCreate(query, initialData, cb); + }; - var model = ctx.instance; - if (!model.getId() && model.seq === undefined) { - model.constructor.current(function(err, seq) { - if (err) return next(err); - model.seq = seq + 1; - next(); + /** + * Increase the current checkpoint if it already exists otherwise initialize it + * @callback {Function} callback + * @param {Error} err + * @param {Object} checkpoint The current checkpoint + */ + Checkpoint.bumpLastSeq = function(cb) { + var Checkpoint = this; + Checkpoint._getSingleton(function(err, cp) { + if (err) return cb(err); + var originalSeq = cp.seq; + cp.seq++; + // Update the checkpoint but only if it was not changed under our hands + Checkpoint.updateAll({id: cp.id, seq: originalSeq}, {seq: cp.seq}, function(err, info) { + if (err) return cb(err); + // possible outcomes + // 1) seq was updated to seq+1 - exactly what we wanted! + // 2) somebody else already updated seq to seq+1 and our call was a no-op. + // That should be ok, checkpoints are time based, so we reuse the one created just now + // 3) seq was bumped more than once, so we will be using a value that is behind the latest seq. + // @bajtos is not entirely sure if this is ok, but since it wasn't handled by the current implementation either, + // he thinks we can keep it this way. + cb(null, cp); }); - } else { - next(); - } - }); + }); + }; }; diff --git a/lib/persisted-model.js b/lib/persisted-model.js index ab17ef16c..bcac8e18d 100644 --- a/lib/persisted-model.js +++ b/lib/persisted-model.js @@ -898,12 +898,7 @@ module.exports = function(registry) { PersistedModel.checkpoint = function(cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); - this.getSourceId(function(err, sourceId) { - if (err) return cb(err); - Checkpoint.create({ - sourceId: sourceId - }, cb); - }); + Checkpoint.bumpLastSeq(cb); }; /** diff --git a/test/checkpoint.test.js b/test/checkpoint.test.js index 98e248efb..c824d02ea 100644 --- a/test/checkpoint.test.js +++ b/test/checkpoint.test.js @@ -1,20 +1,22 @@ var async = require('async'); var loopback = require('../'); +var expect = require('chai').expect; -// create a unique Checkpoint model var Checkpoint = loopback.Checkpoint.extend('TestCheckpoint'); -var memory = loopback.createDataSource({ - connector: loopback.Memory -}); -Checkpoint.attachTo(memory); - describe('Checkpoint', function() { - describe('current()', function() { + describe('bumpLastSeq() and current()', function() { + beforeEach(function() { + var memory = loopback.createDataSource({ + connector: loopback.Memory + }); + Checkpoint.attachTo(memory); + }); + it('returns the highest `seq` value', function(done) { async.series([ - Checkpoint.create.bind(Checkpoint), - Checkpoint.create.bind(Checkpoint), + Checkpoint.bumpLastSeq.bind(Checkpoint), + Checkpoint.bumpLastSeq.bind(Checkpoint), function(next) { Checkpoint.current(function(err, seq) { if (err) next(err); @@ -24,5 +26,59 @@ describe('Checkpoint', function() { } ], done); }); + + it('Should be no race condition for current() when calling in parallel', function(done) { + async.parallel([ + function(next) { Checkpoint.current(next); }, + function(next) { Checkpoint.current(next); } + ], function(err, list) { + if (err) return done(err); + Checkpoint.find(function(err, data) { + if (err) return done(err); + expect(data).to.have.length(1); + done(); + }); + }); + }); + + it('Should be no race condition for bumpLastSeq() when calling in parallel', function(done) { + async.parallel([ + function(next) { Checkpoint.bumpLastSeq(next); }, + function(next) { Checkpoint.bumpLastSeq(next); } + ], function(err, list) { + if (err) return done(err); + Checkpoint.find(function(err, data) { + if (err) return done(err); + // The invariant "we have at most 1 checkpoint instance" is preserved + // even when multiple calls are made in parallel + expect(data).to.have.length(1); + // There is a race condition here, we could end up with both 2 or 3 as the "seq". + // The current implementation of the memory connector always yields 2 though. + expect(data[0].seq).to.equal(2); + // In this particular case, since the new last seq is always 2, both results + // should be 2. + expect(list.map(function(it) {return it.seq;})) + .to.eql([2, 2]); + done(); + }); + }); + }); + + it('Checkpoint.current() for non existing checkpoint should initialize checkpoint', function(done) { + Checkpoint.current(function(err, seq) { + expect(seq).to.equal(1); + done(err); + }); + }); + + it('bumpLastSeq() works when singleton instance does not exists yet', function(done) { + Checkpoint.bumpLastSeq(function(err, cp) { + // We expect `seq` to be 2 since `checkpoint` does not exist and + // `bumpLastSeq` for the first time not only initializes it to one, + // but also increments the initialized value by one. + expect(cp.seq).to.equal(2); + done(err); + }); + }); }); }); From 17bd1016910780cafbab463c669ea70fbd49d8ef Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Thu, 31 Dec 2015 16:52:31 -0800 Subject: [PATCH 03/35] ensure app is booted before integration tests --- test/access-control.integration.js | 6 ++++++ test/relations.integration.js | 6 ++++++ test/remoting.integration.js | 6 ++++++ test/user.integration.js | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/test/access-control.integration.js b/test/access-control.integration.js index 3125a6535..7e417bfe3 100644 --- a/test/access-control.integration.js +++ b/test/access-control.integration.js @@ -11,6 +11,12 @@ var CURRENT_USER = {email: 'current@test.test', password: 'test'}; var debug = require('debug')('loopback:test:access-control.integration'); describe('access control - integration', function() { + before(function(done) { + if (app.booting) { + return app.once('booted', done); + } + done(); + }); lt.beforeEach.withApp(app); diff --git a/test/relations.integration.js b/test/relations.integration.js index 70f1ae122..5257338b4 100644 --- a/test/relations.integration.js +++ b/test/relations.integration.js @@ -11,6 +11,12 @@ var debug = require('debug')('loopback:test:relations.integration'); var async = require('async'); describe('relations - integration', function() { + before(function(done) { + if (app.booting) { + return app.once('booted', done); + } + done(); + }); lt.beforeEach.withApp(app); diff --git a/test/remoting.integration.js b/test/remoting.integration.js index c3288d799..b2b92cd81 100644 --- a/test/remoting.integration.js +++ b/test/remoting.integration.js @@ -6,6 +6,12 @@ var app = require(path.join(SIMPLE_APP, 'server/server.js')); var assert = require('assert'); describe('remoting - integration', function() { + before(function(done) { + if (app.booting) { + return app.once('booted', done); + } + done(); + }); lt.beforeEach.withApp(app); lt.beforeEach.givenModel('store'); diff --git a/test/user.integration.js b/test/user.integration.js index b2a92537b..fd4a198bc 100644 --- a/test/user.integration.js +++ b/test/user.integration.js @@ -7,6 +7,12 @@ var app = require(path.join(SIMPLE_APP, 'server/server.js')); var expect = require('chai').expect; describe('users - integration', function() { + before(function(done) { + if (app.booting) { + return app.once('booted', done); + } + done(); + }); lt.beforeEach.withApp(app); From aff49ff63fe37203658df7bf10e7942128ca6406 Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Thu, 31 Dec 2015 16:53:40 -0800 Subject: [PATCH 04/35] test: fail on error instead of crash If the supertest request fails its basic assertions, there may not even be a body to perform checks against, so bail early when possible. --- test/relations.integration.js | 52 ++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/test/relations.integration.js b/test/relations.integration.js index 5257338b4..dbb2e2556 100644 --- a/test/relations.integration.js +++ b/test/relations.integration.js @@ -97,6 +97,7 @@ describe('relations - integration', function() { this.get(url) .query({'filter': {'include' : 'pictures'}}) .expect(200, function(err, res) { + if (err) return done(err); // console.log(res.body); expect(res.body.name).to.be.equal('Reader 1'); expect(res.body.pictures).to.be.eql([ @@ -112,6 +113,7 @@ describe('relations - integration', function() { this.get(url) .query({'filter': {'include' : 'imageable'}}) .expect(200, function(err, res) { + if (err) return done(err); // console.log(res.body); expect(res.body[0].name).to.be.equal('Picture 1'); expect(res.body[1].name).to.be.equal('Picture 2'); @@ -125,6 +127,7 @@ describe('relations - integration', function() { this.get(url) .query({'filter': {'include' : {'relation': 'imageable', 'scope': { 'include' : 'team'}}}}) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body[0].name).to.be.equal('Picture 1'); expect(res.body[1].name).to.be.equal('Picture 2'); expect(res.body[0].imageable.name).to.be.eql('Reader 1'); @@ -139,6 +142,7 @@ describe('relations - integration', function() { it('should invoke scoped methods remotely', function(done) { this.get('/api/stores/superStores') .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.array; done(); }); @@ -375,7 +379,7 @@ describe('relations - integration', function() { self.url = root.relUrl; self.patient = root.patient; self.physician = root.physician; - done(); + done(err); }); }); @@ -414,7 +418,7 @@ describe('relations - integration', function() { self.url = root.relUrl; self.patient = root.patient; self.physician = root.physician; - done(); + done(err); }); }); @@ -459,7 +463,7 @@ describe('relations - integration', function() { self.url = root.relUrl; self.patient = root.patient; self.physician = root.physician; - done(); + done(err); }); }); @@ -479,7 +483,7 @@ describe('relations - integration', function() { '/patients/rel/' + '999'; self.patient = root.patient; self.physician = root.physician; - done(); + done(err); }); }); @@ -498,7 +502,7 @@ describe('relations - integration', function() { self.url = root.relUrl; self.patient = root.patient; self.physician = root.physician; - done(); + done(err); }); }); @@ -553,7 +557,7 @@ describe('relations - integration', function() { '/patients/' + root.patient.id; self.patient = root.patient; self.physician = root.physician; - done(); + done(err); }); }); @@ -574,7 +578,7 @@ describe('relations - integration', function() { '/patients/' + root.patient.id; self.patient = root.patient; self.physician = root.physician; - done(); + done(err); }); }); @@ -690,6 +694,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.have.property('products'); expect(res.body.products).to.eql([ { @@ -709,6 +714,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.have.property('products'); expect(res.body.products).to.eql([ { @@ -770,6 +776,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body.name).to.be.equal('Group 1'); expect(res.body.poster).to.be.eql( { url: 'http://image.url' } @@ -783,6 +790,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql( { url: 'http://image.url' } ); @@ -806,6 +814,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql( { url: 'http://changed.url' } ); @@ -863,6 +872,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body.name).to.be.equal('List A'); expect(res.body.todoItems).to.be.eql([ { content: 'Todo 1', id: 1 }, @@ -877,6 +887,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { content: 'Todo 1', id: 1 }, { content: 'Todo 2', id: 2 } @@ -891,6 +902,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { content: 'Todo 2', id: 2 } ]); @@ -916,6 +928,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { content: 'Todo 1', id: 1 }, { content: 'Todo 2', id: 2 }, @@ -930,6 +943,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql( { content: 'Todo 3', id: 3 } ); @@ -952,6 +966,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { content: 'Todo 1', id: 1 }, { content: 'Todo 3', id: 3 } @@ -963,6 +978,7 @@ describe('relations - integration', function() { it('returns a 404 response when embedded model is not found', function(done) { var url = '/api/todo-lists/' + this.todoList.id + '/items/2'; this.get(url).expect(404, function(err, res) { + if (err) return done(err); expect(res.body.error.status).to.be.equal(404); expect(res.body.error.message).to.be.equal('Unknown "todoItem" id "2".'); expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND'); @@ -1050,6 +1066,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body.ingredientIds).to.eql([test.ingredient1]); expect(res.body).to.not.have.property('ingredients'); done(); @@ -1075,6 +1092,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Sugar', id: test.ingredient2 }, @@ -1090,6 +1108,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Butter', id: test.ingredient3 } @@ -1105,6 +1124,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { name: 'Butter', id: test.ingredient3 } ]); @@ -1119,6 +1139,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body.ingredientIds).to.eql([ test.ingredient1, test.ingredient3 ]); @@ -1137,6 +1158,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql( { name: 'Butter', id: test.ingredient3 } ); @@ -1152,6 +1174,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body.ingredientIds).to.eql(expected); expect(res.body).to.not.have.property('ingredients'); done(); @@ -1175,6 +1198,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Sugar', id: test.ingredient2 } @@ -1189,6 +1213,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 } ]); @@ -1216,6 +1241,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Sugar', id: test.ingredient2 } @@ -1241,6 +1267,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { name: 'Sugar', id: test.ingredient2 } ]); @@ -1254,6 +1281,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Sugar', id: test.ingredient2 } @@ -1267,6 +1295,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { + if (err) return done(err); expect(err).to.not.exist; expect(res.body.name).to.equal('Photo 1'); done(); @@ -1401,6 +1430,7 @@ describe('relations - integration', function() { var test = this; this.get('/api/books/' + test.book.id + '/pages') .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.an.array; expect(res.body).to.have.length(1); expect(res.body[0].name).to.equal('Page 1'); @@ -1412,6 +1442,7 @@ describe('relations - integration', function() { var test = this; this.get('/api/pages/' + test.page.id + '/notes/' + test.note.id) .expect(200, function(err, res) { + if (err) return done(err); expect(res.headers['x-before']).to.equal('before'); expect(res.headers['x-after']).to.equal('after'); expect(res.body).to.be.an.object; @@ -1424,6 +1455,7 @@ describe('relations - integration', function() { var test = this; this.get('/api/books/unknown/pages/' + test.page.id + '/notes') .expect(404, function(err, res) { + if (err) return done(err); expect(res.body.error).to.be.an.object; var expected = 'could not find a model with id unknown'; expect(res.body.error.message).to.equal(expected); @@ -1436,6 +1468,7 @@ describe('relations - integration', function() { var test = this; this.get('/api/images/' + test.image.id + '/book/pages') .end(function(err, res) { + if (err) return done(err); expect(res.body).to.be.an.array; expect(res.body).to.have.length(1); expect(res.body[0].name).to.equal('Page 1'); @@ -1447,6 +1480,7 @@ describe('relations - integration', function() { var test = this; this.get('/api/images/' + test.image.id + '/book/pages/' + test.page.id) .end(function(err, res) { + if (err) return done(err); expect(res.body).to.be.an.object; expect(res.body.name).to.equal('Page 1'); done(); @@ -1457,6 +1491,7 @@ describe('relations - integration', function() { var test = this; this.get('/api/books/' + test.book.id + '/pages/' + test.page.id + '/notes') .expect(200, function(err, res) { + if (err) return done(err); expect(res.body).to.be.an.array; expect(res.body).to.have.length(1); expect(res.body[0].text).to.equal('Page Note 1'); @@ -1468,6 +1503,7 @@ describe('relations - integration', function() { var test = this; this.get('/api/books/' + test.book.id + '/pages/' + test.page.id + '/notes/' + test.note.id) .expect(200, function(err, res) { + if (err) return done(err); expect(res.headers['x-before']).to.equal('before'); expect(res.headers['x-after']).to.equal('after'); expect(res.body).to.be.an.object; @@ -1480,6 +1516,7 @@ describe('relations - integration', function() { var test = this; this.get('/api/books/' + test.book.id + '/chapters/' + test.chapter.id + '/notes/' + test.cnote.id) .expect(200, function(err, res) { + if (err) return done(err); expect(res.headers['x-before']).to.empty; expect(res.headers['x-after']).to.empty; done(); @@ -1503,6 +1540,7 @@ describe('relations - integration', function() { var test = this; this.get('/api/books/' + test.book.id + '/pages/' + this.page.id + '/throws') .end(function(err, res) { + if (err) return done(err); expect(res.body).to.be.an('object'); expect(res.body.error).to.be.an('object'); expect(res.body.error.name).to.equal('Error'); From db0678baa68bf8ae9168e65fb3ab958d9f156bf1 Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Thu, 7 Jan 2016 13:29:27 -0800 Subject: [PATCH 05/35] test: use ephemeral port for e2e server --- Gruntfile.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index a7a755c81..3df919962 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -217,7 +217,10 @@ module.exports = function(grunt) { grunt.registerTask('e2e-server', function() { var done = this.async(); var app = require('./test/fixtures/e2e/app'); - app.listen(3000, done); + app.listen(0, function() { + process.env.PORT = this.address().port; + done(); + }); }); grunt.registerTask('e2e', ['e2e-server', 'karma:e2e']); From a0a1083564c84aaeb6ce7aa56808e6378e418e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 26 Jan 2016 13:51:37 +0100 Subject: [PATCH 06/35] Hide verificationToken We should never be showing this publically. Adds unit test for hiding verification token. This is a back-port of pull request #1851 from gausie/patch-4 --- common/models/user.json | 2 +- test/user.test.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/common/models/user.json b/common/models/user.json index d70a89d3f..16545ab4b 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -32,7 +32,7 @@ "options": { "caseSensitiveEmail": true }, - "hidden": ["password"], + "hidden": ["password", "verificationToken"], "acls": [ { "principalType": "ROLE", diff --git a/test/user.test.js b/test/user.test.js index 8e2b14948..79f6547ee 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -1320,6 +1320,12 @@ describe('User', function() { }); }); + it('should hide verification tokens from user JSON', function(done) { + var user = new User({email: 'bar@bat.com', password: 'bar', verificationToken: 'a-token' }); + var data = user.toJSON(); + assert(!('verificationToken' in data)); + done(); + }); }); describe('User.confirm(options, fn)', function() { From 015e9cb80e9e7a537891219d8b50f9b94f9e0112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 26 Jan 2016 13:52:57 +0100 Subject: [PATCH 07/35] Correct JSDoc findOrCreate() callback in PersistedModel Update PersistedModel.findOrCreate() JSDoc to reflect the callback accepts an additional created boolean parameter. This is a back-port of pull request #1983 from noderat/patch-1 --- lib/persisted-model.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/persisted-model.js b/lib/persisted-model.js index bcac8e18d..5dceeec04 100644 --- a/lib/persisted-model.js +++ b/lib/persisted-model.js @@ -135,9 +135,10 @@ module.exports = function(registry) { *
See * [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforqueries). * @param {Object} data Data to insert if object matching the `where` filter is not found. - * @callback {Function} callback Callback function called with `cb(err, instance)` arguments. Required. + * @callback {Function} callback Callback function called with `cb(err, instance, created)` arguments. Required. * @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object). * @param {Object} instance Model instance matching the `where` filter, if found. + * @param {Boolean} created True if the instance matching the `where` filter was created. */ PersistedModel.findOrCreate = function findOrCreate(query, data, callback) { From 4753373f4fb03f820c0a66f96258fcfadb900cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 4 Feb 2016 16:28:01 +0100 Subject: [PATCH 08/35] Travis: drop iojs, add v4.x and v5.x --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 168369451..e918e73fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,6 @@ language: node_js node_js: - "0.10" - "0.12" - - "iojs" + - "4" + - "5" From 76ec49c96bd529715683ec2b6992f8a8f0429607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 4 Feb 2016 16:52:50 +0100 Subject: [PATCH 09/35] Fix race condition in error handler test --- test/error-handler.test.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/test/error-handler.test.js b/test/error-handler.test.js index d19abf47e..0494f2ef3 100644 --- a/test/error-handler.test.js +++ b/test/error-handler.test.js @@ -41,15 +41,23 @@ describe('loopback.errorHandler(options)', function() { //arrange var app = loopback(); app.use(loopback.urlNotFound()); - app.use(loopback.errorHandler({ includeStack: false, log: customLogger })); - //act - request(app).get('/url-does-not-exist').end(); + var errorLogged; + app.use(loopback.errorHandler({ + includeStack: false, + log: function customLogger(err, str, req) { + errorLogged = err; + } + })); - //assert - function customLogger(err, str, req) { - assert.ok(err.message === 'Cannot GET /url-does-not-exist'); + //act + request(app).get('/url-does-not-exist').end(function(err) { + if (err) return done(err); + //assert + expect(errorLogged) + .to.have.property('message', 'Cannot GET /url-does-not-exist'); done(); - } + }); + }); }); From 7a54da58708791a1bf92630a4b21ce7a270283cf Mon Sep 17 00:00:00 2001 From: Jue Hou Date: Wed, 27 Jan 2016 14:42:47 -0500 Subject: [PATCH 10/35] Promisify Model Change * Change.diff * Change.findOrCreateChange * Change.rectifyModelChanges * Change.prototype.currentRevision * Change.prototype.rectify --- common/models/change.js | 19 +++++-- test/change.test.js | 111 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/common/models/change.js b/common/models/change.js index be353a039..3b347ec5c 100644 --- a/common/models/change.js +++ b/common/models/change.js @@ -4,6 +4,7 @@ var PersistedModel = require('../../lib/loopback').PersistedModel; var loopback = require('../../lib/loopback'); +var utils = require('../../lib/utils'); var crypto = require('crypto'); var CJSON = {stringify: require('canonical-json')}; var async = require('async'); @@ -77,6 +78,8 @@ module.exports = function(Change) { var Change = this; var errors = []; + callback = callback || utils.createPromiseCallback(); + var tasks = modelIds.map(function(id) { return function(cb) { Change.findOrCreateChange(modelName, id, function(err, change) { @@ -111,6 +114,7 @@ module.exports = function(Change) { } callback(); }); + return callback.promise; }; /** @@ -138,6 +142,7 @@ module.exports = function(Change) { Change.findOrCreateChange = function(modelName, modelId, callback) { assert(loopback.findModel(modelName), modelName + ' does not exist'); + callback = callback || utils.createPromiseCallback(); var id = this.idForModel(modelName, modelId); var Change = this; @@ -155,6 +160,7 @@ module.exports = function(Change) { Change.updateOrCreate(ch, callback); } }); + return callback.promise; }; /** @@ -171,9 +177,7 @@ module.exports = function(Change) { change.debug('rectify change'); - cb = cb || function(err) { - if (err) throw new Error(err); - }; + cb = cb || utils.createPromiseCallback(); change.currentRevision(function(err, rev) { if (err) return cb(err); @@ -194,6 +198,7 @@ module.exports = function(Change) { } ); }); + return cb.promise; function doRectify(checkpoint, rev) { if (rev) { @@ -248,6 +253,7 @@ module.exports = function(Change) { */ Change.prototype.currentRevision = function(cb) { + cb = cb || utils.createPromiseCallback(); var model = this.getModelCtor(); var id = this.getModelId(); model.findById(id, function(err, inst) { @@ -258,6 +264,7 @@ module.exports = function(Change) { cb(null, null); } }); + return cb.promise; }; /** @@ -390,8 +397,11 @@ module.exports = function(Change) { */ Change.diff = function(modelName, since, remoteChanges, callback) { + callback = callback || utils.createPromiseCallback(); + if (!Array.isArray(remoteChanges) || remoteChanges.length === 0) { - return callback(null, {deltas: [], conflicts: []}); + callback(null, {deltas: [], conflicts: []}); + return callback.promise; } var remoteChangeIndex = {}; var modelIds = []; @@ -455,6 +465,7 @@ module.exports = function(Change) { conflicts: conflicts }); }); + return callback.promise; }; /** diff --git a/test/change.test.js b/test/change.test.js index 55d551c72..2f43c037f 100644 --- a/test/change.test.js +++ b/test/change.test.js @@ -82,6 +82,38 @@ describe('Change', function() { }); }); + describe('Change.rectifyModelChanges - promise variant', function() { + describe('using an existing untracked model', function() { + beforeEach(function(done) { + var test = this; + Change.rectifyModelChanges(this.modelName, [this.modelId]) + .then(function(trackedChanges) { + done(); + }) + .catch(done); + }); + + it('should create an entry', function(done) { + var test = this; + Change.find() + .then(function(trackedChanges) { + assert.equal(trackedChanges[0].modelId, test.modelId.toString()); + done(); + }) + .catch(done); + }); + + it('should only create one change', function(done) { + Change.count() + .then(function(count) { + assert.equal(count, 1); + done(); + }) + .catch(done); + }); + }); + }); + describe('Change.findOrCreateChange(modelName, modelId, callback)', function() { describe('when a change doesnt exist', function() { @@ -104,6 +136,27 @@ describe('Change', function() { }); }); + describe('when a change doesnt exist - promise variant', function() { + beforeEach(function(done) { + var test = this; + Change.findOrCreateChange(this.modelName, this.modelId) + .then(function(result) { + test.result = result; + done(); + }) + .catch(done); + }); + + it('should create an entry', function(done) { + var test = this; + Change.findById(this.result.id, function(err, change) { + if (err) return done(err); + assert.equal(change.id, test.result.id); + done(); + }); + }); + }); + describe('when a change does exist', function() { beforeEach(function(done) { var test = this; @@ -219,6 +272,28 @@ describe('Change', function() { }); }); + describe('change.rectify - promise variant', function() { + var change; + beforeEach(function(done) { + Change.findOrCreateChange(this.modelName, this.modelId) + .then(function(ch) { + change = ch; + done(); + }) + .catch(done); + }); + + it('should create a new change with the correct revision', function(done) { + var test = this; + change.rectify() + .then(function(ch) { + assert.equal(ch.rev, test.revisionForModel); + done(); + }) + .catch(done); + }); + }); + describe('change.currentRevision(callback)', function() { it('should get the correct revision', function(done) { var test = this; @@ -234,6 +309,23 @@ describe('Change', function() { }); }); + describe('change.currentRevision - promise variant', function() { + it('should get the correct revision', function(done) { + var test = this; + var change = new Change({ + modelName: this.modelName, + modelId: this.modelId + }); + + change.currentRevision() + .then(function(rev) { + assert.equal(rev, test.revisionForModel); + done(); + }) + .catch(done); + }); + }); + describe('Change.hash(str)', function() { // todo(ritch) test other hashing algorithms it('should hash the given string', function() { @@ -374,6 +466,25 @@ describe('Change', function() { }); }); + it('should return delta and conflict lists - promise variant', function(done) { + var remoteChanges = [ + // an update => should result in a delta + {rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1}, + // no change => should not result in a delta / conflict + {rev: 'bar', prev: 'bar', modelName: this.modelName, modelId: 10, checkpoint: 1}, + // a conflict => should result in a conflict + {rev: 'bat2', prev: 'bat0', modelName: this.modelName, modelId: 11, checkpoint: 1}, + ]; + + Change.diff(this.modelName, 0, remoteChanges) + .then(function(diff) { + assert.equal(diff.deltas.length, 1); + assert.equal(diff.conflicts.length, 1); + done(); + }) + .catch(done); + }); + it('should set "prev" to local revision in non-conflicting delta', function(done) { var updateRecord = { rev: 'foo-new', From a0806eab89c2f62cba93e6579cd9b9c9a78ff4b2 Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Thu, 4 Feb 2016 08:21:15 -0800 Subject: [PATCH 11/35] test: remove errant console.log from test Using console.log like this can result in invalid xml when the xunit reporter is used. [Backport of pull request #2035] --- test/user.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/user.test.js b/test/user.test.js index 79f6547ee..f306332cb 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -1042,7 +1042,6 @@ describe('User', function() { user.verify(options) .then(function(result) { - console.log('here in then function'); assert(result.email); assert(result.email.response); assert(result.token); From e98ed99fe76621c82b5b4b3d690c51c726554caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 5 Feb 2016 09:21:25 +0100 Subject: [PATCH 12/35] Fix race condition in replication tests --- test/replication.test.js | 98 ++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/test/replication.test.js b/test/replication.test.js index 2606aaf53..ecdffbf8d 100644 --- a/test/replication.test.js +++ b/test/replication.test.js @@ -67,40 +67,26 @@ describe('Replication / Change APIs', function() { }); it('should call rectifyAllChanges if no id is passed for rectifyOnDelete', function(done) { - SourceModel.rectifyChange = function() { - return done(new Error('Should not call rectifyChange')); - }; - SourceModel.rectifyAllChanges = function() { - return done(); - }; + var calls = mockSourceModelRectify(); SourceModel.destroyAll({name: 'John'}, function(err, data) { - if (err) - return done(err); + if (err) return done(err); + expect(calls).to.eql(['rectifyAllChanges']); + done(); }); }); it('should call rectifyAllChanges if no id is passed for rectifyOnSave', function(done) { - SourceModel.rectifyChange = function() { - return done(new Error('Should not call rectifyChange')); - }; - SourceModel.rectifyAllChanges = function() { - return done(); - }; + var calls = mockSourceModelRectify(); var newData = {'name': 'Janie'}; SourceModel.update({name: 'Jane'}, newData, function(err, data) { - if (err) - return done(err); + if (err) return done(err); + expect(calls).to.eql(['rectifyAllChanges']); + done(); }); }); it('rectifyOnDelete for Delete should call rectifyChange instead of rectifyAllChanges', function(done) { - TargetModel.rectifyChange = function() { - return done(); - }; - TargetModel.rectifyAllChanges = function() { - return done(new Error('Should not call rectifyAllChanges')); - }; - + var calls = mockTargetModelRectify(); async.waterfall([ function(callback) { SourceModel.destroyAll({name: 'John'}, callback); @@ -110,19 +96,14 @@ describe('Replication / Change APIs', function() { // replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation } ], function(err, results) { - if (err) - return done(err); + if (err) return done(err); + expect(calls).to.eql(['rectifyChange']); + done(); }); }); it('rectifyOnSave for Update should call rectifyChange instead of rectifyAllChanges', function(done) { - TargetModel.rectifyChange = function() { - return done(); - }; - TargetModel.rectifyAllChanges = function() { - return done(new Error('Should not call rectifyAllChanges')); - }; - + var calls = mockTargetModelRectify(); var newData = {'name': 'Janie'}; async.waterfall([ function(callback) { @@ -131,20 +112,16 @@ describe('Replication / Change APIs', function() { function(data, callback) { SourceModel.replicate(TargetModel, callback); // replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation - }], function(err, result) { - if (err) - return done(err); + } + ], function(err, result) { + if (err) return done(err); + expect(calls).to.eql(['rectifyChange']); + done(); }); }); it('rectifyOnSave for Create should call rectifyChange instead of rectifyAllChanges', function(done) { - TargetModel.rectifyChange = function() { - return done(); - }; - TargetModel.rectifyAllChanges = function() { - return done(new Error('Should not call rectifyAllChanges')); - }; - + var calls = mockTargetModelRectify(); var newData = [{name: 'Janie', surname: 'Doe'}]; async.waterfall([ function(callback) { @@ -155,10 +132,43 @@ describe('Replication / Change APIs', function() { // replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation } ], function(err, result) { - if (err) - return done(err); + if (err) return done(err); + expect(calls).to.eql(['rectifyChange']); + done(); }); }); + + function mockSourceModelRectify() { + var calls = []; + + SourceModel.rectifyChange = function(id, cb) { + calls.push('rectifyChange'); + process.nextTick(cb); + }; + + SourceModel.rectifyAllChanges = function(cb) { + calls.push('rectifyAllChanges'); + process.nextTick(cb); + }; + + return calls; + } + + function mockTargetModelRectify() { + var calls = []; + + TargetModel.rectifyChange = function(id, cb) { + calls.push('rectifyChange'); + process.nextTick(cb); + }; + + TargetModel.rectifyAllChanges = function(cb) { + calls.push('rectifyAllChanges'); + process.nextTick(cb); + }; + + return calls; + } }); describe('Model.changes(since, filter, callback)', function() { From 70aec01e844f610aca6b029d62a1d2c3684ce111 Mon Sep 17 00:00:00 2001 From: Candy Date: Thu, 18 Feb 2016 10:38:54 -0500 Subject: [PATCH 13/35] Remove sl-blip from dependency --- package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index bccf39157..e5a08074d 100644 --- a/package.json +++ b/package.json @@ -102,8 +102,5 @@ "depd": "loopback-datasource-juggler/lib/browser.depd.js", "bcrypt": false }, - "license": "MIT", - "optionalDependencies": { - "sl-blip": "http://blip.strongloop.com/loopback@2.26.2" - } + "license": "MIT" } From 97f376c23934930a421ff8c81e620aa04eb7aeb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 19 Feb 2016 10:29:24 +0100 Subject: [PATCH 14/35] 2.27.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove sl-blip from dependency (Candy) * Fix race condition in replication tests (Miroslav Bajtoš) * test: remove errant console.log from test (Ryan Graham) * Promisify Model Change (Jue Hou) * Fix race condition in error handler test (Miroslav Bajtoš) * Travis: drop iojs, add v4.x and v5.x (Miroslav Bajtoš) * Correct JSDoc findOrCreate() callback in PersistedModel (Miroslav Bajtoš) * Hide verificationToken (Miroslav Bajtoš) * test: use ephemeral port for e2e server (Ryan Graham) * test: fail on error instead of crash (Ryan Graham) * ensure app is booted before integration tests (Ryan Graham) * Checkpoint speedup (Amir Jafarian) * Pull in API doc fix from PR into master #1910 (crandmck) --- CHANGES.md | 38 ++++++++++++++++++++++++++++++-------- package.json | 2 +- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ea87f5de4..914da0ae3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,33 @@ +2016-02-19, Version 2.27.0 +========================== + + * Remove sl-blip from dependency (Candy) + + * Fix race condition in replication tests (Miroslav Bajtoš) + + * test: remove errant console.log from test (Ryan Graham) + + * Promisify Model Change (Jue Hou) + + * Fix race condition in error handler test (Miroslav Bajtoš) + + * Travis: drop iojs, add v4.x and v5.x (Miroslav Bajtoš) + + * Correct JSDoc findOrCreate() callback in PersistedModel (Miroslav Bajtoš) + + * Hide verificationToken (Miroslav Bajtoš) + + * test: use ephemeral port for e2e server (Ryan Graham) + + * test: fail on error instead of crash (Ryan Graham) + + * ensure app is booted before integration tests (Ryan Graham) + + * Checkpoint speedup (Amir Jafarian) + + * Pull in API doc fix from PR into master #1910 (crandmck) + + 2015-12-22, Version 2.26.2 ========================== @@ -989,8 +1019,6 @@ 2014-07-15, Version 2.0.0-beta6 =============================== - * 2.0.0-beta6 (Miroslav Bajtoš) - * lib/application: publish Change models to REST API (Miroslav Bajtoš) * models/change: fix typo (Miroslav Bajtoš) @@ -1001,8 +1029,6 @@ 2014-07-03, Version 2.0.0-beta5 =============================== - * 2.0.0-beta5 (Miroslav Bajtoš) - * app: update `url` on `listening` event (Miroslav Bajtoš) * Fix "ReferenceError: loopback is not defined" in registry.memory(). (Guilherme Cirne) @@ -1021,8 +1047,6 @@ 2014-06-26, Version 2.0.0-beta4 =============================== - * 2.0.0-beta4 (Miroslav Bajtoš) - * package: upgrade juggler to 2.0.0-beta2 (Miroslav Bajtoš) * Fix loopback in PhantomJS, fix karma tests (Miroslav Bajtoš) @@ -1149,8 +1173,6 @@ 2014-05-28, Version 2.0.0-beta3 =============================== - * 2.0.0-beta3 (Miroslav Bajtoš) - * package.json: fix malformed json (Miroslav Bajtoš) * 2.0.0-beta2 (Ritchie Martori) diff --git a/package.json b/package.json index e5a08074d..b8f87ee89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback", - "version": "2.26.2", + "version": "2.27.0", "description": "LoopBack: Open Source Framework for Node.js", "homepage": "http://loopback.io", "keywords": [ From a4c643e8904afaf2b08fd8d2f9acd50efae2e93d Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Thu, 18 Feb 2016 20:36:07 -0800 Subject: [PATCH 15/35] application: correct spelling of "cannont" [back-port of pull request #2088] --- lib/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/application.js b/lib/application.js index bd3375539..c1932c20f 100644 --- a/lib/application.js +++ b/lib/application.js @@ -397,7 +397,7 @@ function dataSourcesFromConfig(name, config, connectorRegistry, registry) { var connectorPath; assert(typeof config === 'object', - 'cannont create data source without config object'); + 'can not create data source without config object'); if (typeof config.connector === 'string') { name = config.connector; From 50e3578992a81c939a974e313165d78718560593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 26 Feb 2016 14:20:07 +0100 Subject: [PATCH 16/35] Improve error message on connector init error [back-port of pull request #2105] --- lib/application.js | 20 +++++++++++++++----- test/app.test.js | 10 ++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/application.js b/lib/application.js index c1932c20f..dd5fdb730 100644 --- a/lib/application.js +++ b/lib/application.js @@ -219,11 +219,19 @@ app.models = function() { * @param {Object} config The data source config */ app.dataSource = function(name, config) { - var ds = dataSourcesFromConfig(name, config, this.connectors, this.registry); - this.dataSources[name] = - this.dataSources[classify(name)] = - this.dataSources[camelize(name)] = ds; - return ds; + try { + var ds = dataSourcesFromConfig(name, config, this.connectors, this.registry); + this.dataSources[name] = + this.dataSources[classify(name)] = + this.dataSources[camelize(name)] = ds; + return ds; + } catch (err) { + if (err.message) { + err.message = 'Cannot create data source ' + JSON.stringify(name) + + ': ' + err.message; + } + throw err; + } }; /** @@ -410,6 +418,8 @@ function dataSourcesFromConfig(name, config, connectorRegistry, registry) { config.connector = require(connectorPath); } } + if (!config.connector.name) + config.connector.name = name; } return registry.createDataSource(config); diff --git a/test/app.test.js b/test/app.test.js index 2701ef9d2..1144db2eb 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -732,6 +732,16 @@ describe('app', function() { app.dataSource('custom', { connector: 'custom' }); expect(app.dataSources.custom.name).to.equal(loopback.Memory.name); }); + + it('adds data source name to error messages', function() { + app.connector('throwing', { + initialize: function() { throw new Error('expected test error'); }, + }); + + expect(function() { + app.dataSource('bad-ds', { connector: 'throwing' }); + }).to.throw(/bad-ds.*throwing/); + }); }); describe.onServer('listen()', function() { From e4b275243fe11b2d4d756e9f8995fefa66fa8873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Kr=C3=B6ger?= Date: Mon, 29 Feb 2016 16:49:41 +0100 Subject: [PATCH 17/35] Allow built-in token middleware to run repeatedly Add two new options: - When `enableDoublecheck` is true, the middleware will run even if a previous middleware has already set `req.accessToken` (possibly to `null` for anonymous requests) - When `overwriteExistingToken` is true (and `enableDoublecheck` too), the middleware will overwrite `req.accessToken` set by a previous middleware instances. --- server/middleware/token.js | 20 ++++++++- test/access-token.test.js | 86 +++++++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/server/middleware/token.js b/server/middleware/token.js index e80eb560b..146c75d17 100644 --- a/server/middleware/token.js +++ b/server/middleware/token.js @@ -62,6 +62,8 @@ function escapeRegExp(str) { * @property {Array} [headers] Array of header names. * @property {Array} [params] Array of param names. * @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request + * @property {Boolean} [enableDoublecheck] Execute middleware although an instance mounted earlier in the chain didn't find a token + * @property {Boolean} [overwriteExistingToken] only has effect in combination with `enableDoublecheck`. If truthy, will allow to overwrite an existing accessToken. * @property {Function|String} [model] AccessToken model name or class to use. * @property {String} [currentUserLiteral] String literal for the current user. * @header loopback.token([options]) @@ -80,6 +82,9 @@ function token(options) { currentUserLiteral = escapeRegExp(currentUserLiteral); } + var enableDoublecheck = !!options.enableDoublecheck; + var overwriteExistingToken = !!options.overwriteExistingToken; + return function(req, res, next) { var app = req.app; var registry = app.registry; @@ -97,8 +102,19 @@ function token(options) { 'loopback.token() middleware requires a AccessToken model'); if (req.accessToken !== undefined) { - rewriteUserLiteral(req, currentUserLiteral); - return next(); + if (!enableDoublecheck) { + // req.accessToken is defined already (might also be "null" or "false") and enableDoublecheck + // has not been set --> skip searching for credentials + rewriteUserLiteral(req, currentUserLiteral); + return next(); + } + if (req.accessToken && req.accessToken.id && !overwriteExistingToken) { + // req.accessToken.id is defined, which means that some other middleware has identified a valid user. + // when overwriteExistingToken is not set to a truthy value, skip searching for credentials. + rewriteUserLiteral(req, currentUserLiteral); + return next(); + } + // continue normal operation (as if req.accessToken was undefined) } TokenModel.findForRequest(req, options, function(err, token) { req.accessToken = token || null; diff --git a/test/access-token.test.js b/test/access-token.test.js index 9e57cba2a..a2ddbdb09 100644 --- a/test/access-token.test.js +++ b/test/access-token.test.js @@ -65,7 +65,7 @@ describe('loopback.token(options)', function() { .end(done); }); - describe('populating req.toen from HTTP Basic Auth formatted authorization header', function() { + describe('populating req.token from HTTP Basic Auth formatted authorization header', function() { it('parses "standalone-token"', function(done) { var token = this.token.id; token = 'Basic ' + new Buffer(token).toString('base64'); @@ -199,6 +199,90 @@ describe('loopback.token(options)', function() { done(); }); }); + + describe('loading multiple instances of token middleware', function() { + it('should skip when req.token is already present and no further options are set', + function(done) { + var tokenStub = { id: 'stub id' }; + app.use(function(req, res, next) { + req.accessToken = tokenStub; + next(); + }); + app.use(loopback.token({ model: Token })); + app.get('/', function(req, res, next) { + res.send(req.accessToken); + }); + + request(app).get('/') + .set('Authorization', this.token.id) + .expect(200) + .end(function(err, res) { + if (err) return done(err); + expect(res.body).to.eql(tokenStub); + done(); + }); + }); + + it('should not overwrite valid existing token (has "id" property) ' + + ' when overwriteExistingToken is falsy', + function(done) { + var tokenStub = { id: 'stub id' }; + app.use(function(req, res, next) { + req.accessToken = tokenStub; + next(); + }); + app.use(loopback.token({ + model: Token, + enableDoublecheck: true, + })); + app.get('/', function(req, res, next) { + res.send(req.accessToken); + }); + + request(app).get('/') + .set('Authorization', this.token.id) + .expect(200) + .end(function(err, res) { + if (err) return done(err); + expect(res.body).to.eql(tokenStub); + done(); + }); + }); + + it('should overwrite existing token when enableDoublecheck ' + + 'and overwriteExistingToken options are truthy', + function(done) { + var token = this.token; + var tokenStub = { id: 'stub id' }; + + app.use(function(req, res, next) { + req.accessToken = tokenStub; + next(); + }); + app.use(loopback.token({ + model: Token, + enableDoublecheck: true, + overwriteExistingToken: true, + })); + app.get('/', function(req, res, next) { + res.send(req.accessToken); + }); + + request(app).get('/') + .set('Authorization', token.id) + .expect(200) + .end(function(err, res) { + if (err) return done(err); + expect(res.body).to.eql({ + id: token.id, + ttl: token.ttl, + userId: token.userId, + created: token.created.toJSON(), + }); + done(); + }); + }); + }); }); describe('AccessToken', function() { From 1ea1cd612a3a51cfeb73336c7b80d15097a66bb8 Mon Sep 17 00:00:00 2001 From: Tim Needham Date: Sun, 17 Jan 2016 14:42:38 +0000 Subject: [PATCH 18/35] Fix typo in Model.nestRemoting Prevent apps from crashing when using `Model.nestRemoting` without `{ hooks: false }` option. Note that it is not possible to reproduce this bug using our current Mocha test suite, because other tests modify the global state in such way that the bug no longer occurs. [back-port of #2245] --- lib/model.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/model.js b/lib/model.js index f3ec47b25..d6564bf49 100644 --- a/lib/model.js +++ b/lib/model.js @@ -806,8 +806,8 @@ module.exports = function(registry) { listenerTree.before = listenerTree.before || {}; listenerTree.after = listenerTree.after || {}; - var beforeListeners = remotes.listenerTree.before[toModelName] || {}; - var afterListeners = remotes.listenerTree.after[toModelName] || {}; + var beforeListeners = listenerTree.before[toModelName] || {}; + var afterListeners = listenerTree.after[toModelName] || {}; sharedClass.methods().forEach(function(method) { var delegateTo = method.rest && method.rest.delegateTo; From 2498c02f31469eb80f7edce60347b35f0b7a92e0 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Wed, 27 Apr 2016 18:15:24 +0700 Subject: [PATCH 19/35] Add new feature to emit a `remoteMethodDisabled` event when disabling a remote method. Signed-off-by: Supasate Choochaisri --- lib/application.js | 5 +++++ lib/model.js | 1 + package.json | 1 + test/app.test.js | 16 ++++++++++++++++ test/model.test.js | 18 ++++++++++++++++++ 5 files changed, 41 insertions(+) diff --git a/lib/application.js b/lib/application.js index dd5fdb730..c5485fdb1 100644 --- a/lib/application.js +++ b/lib/application.js @@ -153,6 +153,11 @@ app.model = function(Model, config) { this.emit('modelRemoted', Model.sharedClass); } + var self = this; + Model.on('remoteMethodDisabled', function(model, methodName) { + self.emit('remoteMethodDisabled', model, methodName); + }); + Model.shared = isPublic; Model.app = this; Model.emit('attached', this); diff --git a/lib/model.js b/lib/model.js index d6564bf49..685e84045 100644 --- a/lib/model.js +++ b/lib/model.js @@ -431,6 +431,7 @@ module.exports = function(registry) { Model.disableRemoteMethod = function(name, isStatic) { this.sharedClass.disableMethod(name, isStatic || false); + this.emit('remoteMethodDisabled', this.sharedClass, name); }; Model.belongsToRemoting = function(relationName, relation, define) { diff --git a/package.json b/package.json index b8f87ee89..01f16ec48 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "loopback-testing": "~1.1.0", "mocha": "^2.1.0", "sinon": "^1.13.0", + "sinon-chai": "^2.8.0", "strong-task-emitter": "^0.0.6", "supertest": "^0.15.0" }, diff --git a/test/app.test.js b/test/app.test.js index 1144db2eb..347d738b7 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -606,6 +606,22 @@ describe('app', function() { expect(remotedClass).to.eql(Color.sharedClass); }); + it('emits a `remoteMethodDisabled` event', function() { + var Color = PersistedModel.extend('color', { name: String }); + Color.shared = true; + var remoteMethodDisabledClass, disabledRemoteMethod; + app.on('remoteMethodDisabled', function(sharedClass, methodName) { + remoteMethodDisabledClass = sharedClass; + disabledRemoteMethod = methodName; + }); + app.model(Color); + app.models.Color.disableRemoteMethod('findOne'); + expect(remoteMethodDisabledClass).to.exist; + expect(remoteMethodDisabledClass).to.eql(Color.sharedClass); + expect(disabledRemoteMethod).to.exist; + expect(disabledRemoteMethod).to.eql('findOne'); + }); + it.onServer('updates REST API when a new model is added', function(done) { app.use(loopback.rest()); request(app).get('/colors').expect(404, function(err, res) { diff --git a/test/model.test.js b/test/model.test.js index b9a16f36d..37147f40a 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -1,9 +1,13 @@ var async = require('async'); +var chai = require('chai'); +var expect = chai.expect; var loopback = require('../'); var ACL = loopback.ACL; var Change = loopback.Change; var defineModelTestsWithDataSource = require('./util/model-tests'); var PersistedModel = loopback.PersistedModel; +var sinonChai = require('sinon-chai'); +chai.use(sinonChai); var describe = require('./util/describe'); @@ -618,6 +622,20 @@ describe.onServer('Remote Methods', function() { 'createChangeStream' ]); }); + + it('emits a `remoteMethodDisabled` event', function() { + var app = loopback(); + var model = PersistedModel.extend('TestModelForDisablingRemoteMethod'); + app.dataSource('db', { connector: 'memory' }); + app.model(model, { dataSource: 'db' }); + + var callbackSpy = require('sinon').spy(); + var TestModel = app.models.TestModelForDisablingRemoteMethod; + TestModel.on('remoteMethodDisabled', callbackSpy); + TestModel.disableRemoteMethod('findOne'); + + expect(callbackSpy).to.have.been.calledWith(TestModel.sharedClass, 'findOne'); + }); }); describe('Model.getApp(cb)', function() { From 6d738690c82b59476d992d5696a093cca1894aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 2 May 2016 13:00:09 +0200 Subject: [PATCH 20/35] 2.28.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new feature to emit a `remoteMethodDisabled` event when disabling a remote method. (Supasate Choochaisri) * Fix typo in Model.nestRemoting (Tim Needham) * Allow built-in token middleware to run repeatedly (Benjamin Kröger) * Improve error message on connector init error (Miroslav Bajtoš) * application: correct spelling of "cannont" (Sam Roberts) --- CHANGES.md | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 914da0ae3..aa42725cf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,17 @@ +2016-05-02, Version 2.28.0 +========================== + + * Add new feature to emit a `remoteMethodDisabled` event when disabling a remote method. (Supasate Choochaisri) + + * Fix typo in Model.nestRemoting (Tim Needham) + + * Allow built-in token middleware to run repeatedly (Benjamin Kröger) + + * Improve error message on connector init error (Miroslav Bajtoš) + + * application: correct spelling of "cannont" (Sam Roberts) + + 2016-02-19, Version 2.27.0 ========================== diff --git a/package.json b/package.json index 01f16ec48..0ac007a69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback", - "version": "2.27.0", + "version": "2.28.0", "description": "LoopBack: Open Source Framework for Node.js", "homepage": "http://loopback.io", "keywords": [ From 845c59ecedbf34e0375bd21a7854a916bc56c145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 2 May 2016 13:48:12 +0200 Subject: [PATCH 21/35] test/user: use local registry Rework User tests to not depend on `app.autoAttach()` and global shared registry of Models. Instead, each tests creates a fresh app instance with a new in-memory datasource and a new set of Models. --- test/user.test.js | 98 +++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/test/user.test.js b/test/user.test.js index f306332cb..23fca2ad5 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -1,12 +1,6 @@ require('./support'); var loopback = require('../'); -var User; -var AccessToken; -var MailConnector = require('../lib/connectors/mail'); - -var userMemory = loopback.createDataSource({ - connector: 'memory' -}); +var User, AccessToken; describe('User', function() { var validCredentialsEmail = 'foo@bar.com'; @@ -19,45 +13,59 @@ describe('User', function() { var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'}; var incompleteCredentials = {password: 'bar1'}; - var defaultApp; + // Create a local app variable to prevent clashes with the global + // variable shared by all tests. While this should not be necessary if + // the tests were written correctly, it turns out that's not the case :( + var app; + + beforeEach(function setupAppAndModels(done) { + // override the global app object provided by test/support.js + // and create a local one that does not share state with other tests + app = loopback({ localRegistry: true, loadBuiltinModels: true }); + app.dataSource('db', { connector: 'memory' }); + + // setup Email model, it's needed by User tests + app.dataSource('email', { + connector: loopback.Mail, + transports: [{ type: 'STUB' }], + }); + var Email = app.registry.getModel('Email'); + app.model(Email, { dataSource: 'email' }); + + // attach User and related models + User = app.registry.createModel('TestUser', {}, { + base: 'User', + http: { path: 'test-users' }, + }); + app.model(User, { dataSource: 'db' }); - beforeEach(function() { - // FIXME: [rfeng] Remove loopback.User.app so that remote hooks don't go - // to the wrong app instance - defaultApp = loopback.User.app; - loopback.User.app = null; - User = loopback.User.extend('TestUser', {}, {http: {path: 'test-users'}}); - AccessToken = loopback.AccessToken.extend('TestAccessToken'); - User.email = loopback.Email.extend('email'); - loopback.autoAttach(); + AccessToken = app.registry.getModel('AccessToken'); + app.model(AccessToken, { dataSource: 'db' }); + + User.email = Email; // Update the AccessToken relation to use the subclass of User AccessToken.belongsTo(User, {as: 'user', foreignKey: 'userId'}); User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'}); + // Speed up the password hashing algorithm + // for tests using the built-in User model + User.settings.saltWorkFactor = 4; + // allow many User.afterRemote's to be called User.setMaxListeners(0); - }); - - beforeEach(function(done) { - app.enableAuth(); - app.use(loopback.token({model: AccessToken})); + app.enableAuth({ dataSource: 'db' }); + app.use(loopback.token({ model: AccessToken })); app.use(loopback.rest()); app.model(User); User.create(validCredentials, function(err, user) { + if (err) return done(err); User.create(validCredentialsEmailVerified, done); }); }); - afterEach(function(done) { - loopback.User.app = defaultApp; - User.destroyAll(function(err) { - User.accessToken.destroyAll(done); - }); - }); - describe('User.create', function() { it('Create a new user', function(done) { User.create({email: 'f@b.com', password: 'bar'}, function(err, user) { @@ -694,12 +702,19 @@ describe('User', function() { var User; var AccessToken; - before(function() { - User = loopback.User.extend('RealmUser', {}, - {realmRequired: true, realmDelimiter: ':'}); - AccessToken = loopback.AccessToken.extend('RealmAccessToken'); + beforeEach(function() { + User = app.registry.createModel('RealmUser', {}, { + base: 'TestUser', + realmRequired: true, + realmDelimiter: ':', + }); - loopback.autoAttach(); + AccessToken = app.registry.createModel('RealmAccessToken', {}, { + base: 'AccessToken', + }); + + app.model(AccessToken, { dataSource: 'db' }); + app.model(User, { dataSource: 'db' }); // Update the AccessToken relation to use the subclass of User AccessToken.belongsTo(User, {as: 'user', foreignKey: 'userId'}); @@ -770,15 +785,6 @@ describe('User', function() { }); }); - afterEach(function(done) { - User.deleteAll({realm: 'realm1'}, function(err) { - if (err) { - return done(err); - } - User.deleteAll({realm: 'realm2'}, done); - }); - }); - it('rejects a user by without realm', function(done) { User.login(credentialWithoutRealm, function(err, accessToken) { assert(err); @@ -828,11 +834,11 @@ describe('User', function() { }); describe('User.login with realmRequired but no realmDelimiter', function() { - before(function() { + beforeEach(function() { User.settings.realmDelimiter = undefined; }); - after(function() { + afterEach(function() { User.settings.realmDelimiter = ':'; }); @@ -1534,7 +1540,7 @@ describe('User', function() { describe('ctor', function() { it('exports default Email model', function() { expect(User.email, 'User.email').to.be.a('function'); - expect(User.email.modelName, 'modelName').to.eql('email'); + expect(User.email.modelName, 'modelName').to.eql('Email'); }); it('exports default AccessToken model', function() { From cae9786f0eb20bb4e2bb14efad961ad251542df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 2 May 2016 14:30:11 +0200 Subject: [PATCH 22/35] Fix role.isOwner to support app-local registry --- common/models/role.js | 10 ++++------ test/role.test.js | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/common/models/role.js b/common/models/role.js index a176fec17..523e68982 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -147,12 +147,10 @@ module.exports = function(Role) { }); function isUserClass(modelClass) { - if (modelClass) { - return modelClass === loopback.User || - modelClass.prototype instanceof loopback.User; - } else { - return false; - } + if (!modelClass) return false; + var User = modelClass.modelBuilder.models.User; + if (!User) return false; + return modelClass == User || modelClass.prototype instanceof User; } /*! diff --git a/test/role.test.js b/test/role.test.js index 3053a0aff..8e5d1a129 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -407,4 +407,27 @@ describe('role model', function() { }); }); + describe('isOwner', function() { + it('supports app-local model registry', function(done) { + var app = loopback({ localRegistry: true, loadBuiltinModels: true }); + app.dataSource('db', { connector: 'memory' }); + // attach all auth-related models to 'db' datasource + app.enableAuth({ dataSource: 'db' }); + + var Role = app.models.Role; + var User = app.models.User; + + var u = app.registry.findModel('User'); + var credentials = { email: 'test@example.com', password: 'pass' }; + User.create(credentials, function(err, user) { + if (err) return done(err); + + Role.isOwner(User, user.id, user.id, function(err, result) { + if (err) return done(err); + expect(result, 'isOwner result').to.equal(true); + done(); + }); + }); + }); + }); }); From 53cd449c9c1b86f1147765a854d28f2b39be609c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 2 May 2016 14:53:21 +0200 Subject: [PATCH 23/35] test/rest.middleware: use local registry Rework tests in `test/rest.middleware.test.js` to not depend on `app.autoAttach()` and global shared registry of Models. Instead, each tests creates a fresh app instance with a new in-memory datasource and a new set of Models. --- test/rest.middleware.test.js | 78 +++++++++++++++++------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/test/rest.middleware.test.js b/test/rest.middleware.test.js index 5757c44b6..61b935644 100644 --- a/test/rest.middleware.test.js +++ b/test/rest.middleware.test.js @@ -1,11 +1,15 @@ var path = require('path'); describe('loopback.rest', function() { - var MyModel; + var app, MyModel; + beforeEach(function() { - var ds = app.dataSource('db', { connector: loopback.Memory }); - MyModel = ds.createModel('MyModel', {name: String}); - loopback.autoAttach(); + // override the global app object provided by test/support.js + // and create a local one that does not share state with other tests + app = loopback({ localRegistry: true, loadBuiltinModels: true }); + var db = app.dataSource('db', { connector: 'memory' }); + MyModel = app.registry.createModel('MyModel'); + MyModel.attachTo(db); }); it('works out-of-the-box', function(done) { @@ -101,7 +105,7 @@ describe('loopback.rest', function() { }); it('should honour `remoting.rest.supportedTypes`', function(done) { - var app = loopback(); + var app = loopback({ localRegistry: true }); // NOTE it is crucial to set `remoting` before creating any models var supportedTypes = ['json', 'application/javascript', 'text/javascript']; @@ -117,26 +121,24 @@ describe('loopback.rest', function() { }); it('allows models to provide a custom HTTP path', function(done) { - var ds = app.dataSource('db', { connector: loopback.Memory }); - var CustomModel = ds.createModel('CustomModel', + var CustomModel = app.registry.createModel('CustomModel', { name: String }, { http: { 'path': 'domain1/CustomModelPath' } }); - app.model(CustomModel); + app.model(CustomModel, { dataSource: 'db' }); app.use(loopback.rest()); request(app).get('/domain1/CustomModelPath').expect(200).end(done); }); it('should report 200 for url-encoded HTTP path', function(done) { - var ds = app.dataSource('db', { connector: loopback.Memory }); - var CustomModel = ds.createModel('CustomModel', + var CustomModel = app.registry.createModel('CustomModel', { name: String }, { http: { path: 'domain%20one/CustomModelPath' } }); - app.model(CustomModel); + app.model(CustomModel, { dataSource: 'db' }); app.use(loopback.rest()); request(app).get('/domain%20one/CustomModelPath').expect(200).end(done); @@ -144,12 +146,12 @@ describe('loopback.rest', function() { it('includes loopback.token when necessary', function(done) { givenUserModelWithAuth(); - app.enableAuth(); + app.enableAuth({ dataSource: 'db' }); app.use(loopback.rest()); givenLoggedInUser(function(err, token) { if (err) return done(err); - expect(token).instanceOf(app.models.accessToken); + expect(token).instanceOf(app.models.AccessToken); request(app).get('/users/' + token.userId) .set('Authorization', token.id) .expect(200) @@ -268,25 +270,25 @@ describe('loopback.rest', function() { it('should enable context using loopback.context', function(done) { app.use(loopback.context({ enableHttpContext: true })); - app.enableAuth(); + app.enableAuth({ dataSource: 'db' }); app.use(loopback.rest()); invokeGetToken(done); }); it('should enable context with loopback.rest', function(done) { - app.enableAuth(); - app.set('remoting', { context: { enableHttpContext: true } }); + app.enableAuth({ dataSource: 'db' }); + app.set('remoting', { context: { enableHttpContext: true }}); app.use(loopback.rest()); invokeGetToken(done); }); it('should support explicit context', function(done) { - app.enableAuth(); + app.enableAuth({ dataSource: 'db' }); app.use(loopback.context()); app.use(loopback.token( - { model: loopback.getModelByType(loopback.AccessToken) })); + { model: app.registry.getModelByType('AccessToken') })); app.use(function(req, res, next) { loopback.getCurrentContext().set('accessToken', req.accessToken); next(); @@ -321,32 +323,26 @@ describe('loopback.rest', function() { }); function givenUserModelWithAuth() { - // NOTE(bajtos) It is important to create a custom AccessToken model here, - // in order to overwrite the entry created by previous tests in - // the global model registry - app.model('accessToken', { - options: { - base: 'AccessToken' - }, - dataSource: 'db' - }); - return app.model('user', { - options: { - base: 'User', - relations: { - accessTokens: { - model: 'accessToken', - type: 'hasMany', - foreignKey: 'userId' - } - } - }, - dataSource: 'db' - }); + var AccessToken = app.registry.getModel('AccessToken'); + app.model(AccessToken, { dataSource: 'db' }); + var User = app.registry.getModel('User'); + app.model(User, { dataSource: 'db' }); + + // NOTE(bajtos) This is puzzling to me. The built-in User & AccessToken + // models should come with both relations already set up, i.e. the + // following two lines should not be neccessary. + // And it does behave that way when only tests in this file are run. + // However, when I run the full test suite (all files), the relations + // get broken. + AccessToken.belongsTo(User, { as: 'user', foreignKey: 'userId' }); + User.hasMany(AccessToken, { as: 'accessTokens', foreignKey: 'userId' }); + + return User; } + function givenLoggedInUser(cb, done) { var credentials = { email: 'user@example.com', password: 'pwd' }; - var User = app.models.user; + var User = app.models.User; User.create(credentials, function(err, user) { if (err) return done(err); From 6c59390754e776c9f831d06065aa0e7adbaa94b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 3 May 2016 10:19:45 +0200 Subject: [PATCH 24/35] Disable DEBUG output for eslint on Jenkins CI --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 0ac007a69..148d8007a 100644 --- a/package.json +++ b/package.json @@ -103,5 +103,10 @@ "depd": "loopback-datasource-juggler/lib/browser.depd.js", "bcrypt": false }, + "config": { + "ci": { + "debug": "*,-mocha:*,-eslint:*" + } + }, "license": "MIT" } From bd7f2b6db1daf61b144a97e534bbdfcc5b0ee075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 3 May 2016 15:55:37 +0200 Subject: [PATCH 25/35] travis: drop node@5, add node@6 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e918e73fb..d431fa429 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,5 @@ node_js: - "0.10" - "0.12" - "4" - - "5" + - "6" From da2fb0ae15c2b436a26467cde94173f2339eb439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 3 May 2016 16:03:48 +0200 Subject: [PATCH 26/35] app: send port:0 instead of port:undefined Node v6 no longer supports port:undefined, this commit is fixing app.listen() to correctly send port:0 when no port is specified. --- lib/application.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/application.js b/lib/application.js index c5485fdb1..dad8f24f6 100644 --- a/lib/application.js +++ b/lib/application.js @@ -566,7 +566,11 @@ app.listen = function(cb) { (arguments.length == 1 && typeof arguments[0] == 'function'); if (useAppConfig) { - server.listen(this.get('port'), this.get('host'), cb); + var port = this.get('port'); + // NOTE(bajtos) port:undefined no longer works on node@6, + // we must pass port:0 explicitly + if (port === undefined) port = 0; + server.listen(port, this.get('host'), cb); } else { server.listen.apply(server, arguments); } From e2b1f78f1e801ac8f5dc7ed0a8bb594f39979207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 3 May 2016 16:43:45 +0200 Subject: [PATCH 27/35] Upgrade phantomjs to 2.x --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 148d8007a..2eb05c8b3 100644 --- a/package.json +++ b/package.json @@ -78,12 +78,13 @@ "karma-html2js-preprocessor": "^0.1.0", "karma-junit-reporter": "^0.2.2", "karma-mocha": "^0.1.10", - "karma-phantomjs-launcher": "^0.1.4", + "karma-phantomjs-launcher": "^1.0.0", "karma-script-launcher": "^0.1.0", "loopback-boot": "^2.7.0", "loopback-datasource-juggler": "^2.19.1", "loopback-testing": "~1.1.0", "mocha": "^2.1.0", + "phantomjs-prebuilt": "^2.1.7", "sinon": "^1.13.0", "sinon-chai": "^2.8.0", "strong-task-emitter": "^0.0.6", From 553889b378fc9b5f270b61f9fc3711b2607cdc29 Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Tue, 3 May 2016 17:09:47 -0700 Subject: [PATCH 28/35] relicense as MIT only --- LICENSE | 25 +++++++++++++++++++++++++ LICENSE.md | 9 --------- 2 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 LICENSE delete mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..a95641b20 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) IBM Corp. 2013,2016. All Rights Reserved. +Node module: loopback +This project is licensed under the MIT License, full text below. + +-------- + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 29d781523..000000000 --- a/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -Copyright (c) 2013-2015 StrongLoop, Inc and other contributors. - -loopback uses a dual license model. - -You may use this library under the terms of the [MIT License][], -or under the terms of the [StrongLoop Subscription Agreement][]. - -[MIT License]: http://opensource.org/licenses/MIT -[StrongLoop Subscription Agreement]: http://strongloop.com/license From 4d6f2e7ab704baed7a5c37396289485976d93bbb Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Tue, 3 May 2016 17:10:46 -0700 Subject: [PATCH 29/35] update/insert copyright notices --- Gruntfile.js | 5 +++++ browser/current-context.js | 5 +++++ common/models/access-token.js | 5 +++++ common/models/acl.js | 5 +++++ common/models/application.js | 5 +++++ common/models/change.js | 5 +++++ common/models/checkpoint.js | 5 +++++ common/models/email.js | 5 +++++ common/models/role-mapping.js | 5 +++++ common/models/role.js | 5 +++++ common/models/scope.js | 5 +++++ common/models/user.js | 5 +++++ example/client-server/client.js | 5 +++++ example/client-server/models.js | 5 +++++ example/client-server/server.js | 5 +++++ example/colors/app.js | 5 +++++ example/context/app.js | 5 +++++ example/mobile-models/app.js | 5 +++++ example/replication/app.js | 5 +++++ example/simple-data-source/app.js | 5 +++++ index.js | 5 +++++ lib/access-context.js | 5 +++++ lib/application.js | 5 +++++ lib/browser-express.js | 5 +++++ lib/builtin-models.js | 5 +++++ lib/connectors/base-connector.js | 5 +++++ lib/connectors/mail.js | 5 +++++ lib/connectors/memory.js | 5 +++++ lib/express-middleware.js | 5 +++++ lib/loopback.js | 5 +++++ lib/model.js | 5 +++++ lib/persisted-model.js | 5 +++++ lib/registry.js | 5 +++++ lib/runtime.js | 5 +++++ lib/server-app.js | 5 +++++ lib/utils.js | 5 +++++ server/current-context.js | 5 +++++ server/middleware/context.js | 5 +++++ server/middleware/error-handler.js | 5 +++++ server/middleware/favicon.js | 5 +++++ server/middleware/rest.js | 5 +++++ server/middleware/static.js | 5 +++++ server/middleware/status.js | 5 +++++ server/middleware/token.js | 5 +++++ server/middleware/url-not-found.js | 5 +++++ test/access-control.integration.js | 5 +++++ test/access-token.test.js | 5 +++++ test/acl.test.js | 5 +++++ test/app.test.js | 5 +++++ test/change-stream.test.js | 5 +++++ test/change.test.js | 5 +++++ test/checkpoint.test.js | 5 +++++ test/data-source.test.js | 5 +++++ test/e2e/remote-connector.e2e.js | 5 +++++ test/e2e/replication.e2e.js | 5 +++++ test/email.test.js | 5 +++++ test/error-handler.test.js | 5 +++++ test/fixtures/access-control/server/server.js | 5 +++++ test/fixtures/e2e/server/models.js | 5 +++++ test/fixtures/e2e/server/server.js | 5 +++++ .../shared-methods/both-configs-set/common/models/todo.js | 5 +++++ .../shared-methods/both-configs-set/server/server.js | 5 +++++ .../config-default-false/common/models/todo.js | 5 +++++ .../shared-methods/config-default-false/server/server.js | 5 +++++ .../shared-methods/config-default-true/common/models/todo.js | 5 +++++ .../shared-methods/config-default-true/server/server.js | 5 +++++ .../config-defined-false/common/models/todo.js | 5 +++++ .../shared-methods/config-defined-false/server/server.js | 5 +++++ .../shared-methods/config-defined-true/common/models/todo.js | 5 +++++ .../shared-methods/config-defined-true/server/server.js | 5 +++++ .../model-config-default-false/common/models/todo.js | 5 +++++ .../model-config-default-false/server/server.js | 5 +++++ .../model-config-default-true/common/models/todo.js | 5 +++++ .../model-config-default-true/server/server.js | 5 +++++ .../model-config-defined-false/common/models/todo.js | 5 +++++ .../model-config-defined-false/server/server.js | 5 +++++ .../model-config-defined-true/common/models/todo.js | 5 +++++ .../model-config-defined-true/server/server.js | 5 +++++ test/fixtures/simple-app/boot/foo.js | 5 +++++ test/fixtures/simple-app/common/models/bar.js | 5 +++++ test/fixtures/simple-integration-app/server/server.js | 5 +++++ test/fixtures/user-integration-app/server/server.js | 5 +++++ test/geo-point.test.js | 5 +++++ test/hidden-properties.test.js | 5 +++++ test/integration.test.js | 5 +++++ test/karma.conf.js | 5 +++++ test/loopback.test.js | 5 +++++ test/memory.test.js | 5 +++++ test/model.application.test.js | 5 +++++ test/model.test.js | 5 +++++ test/registries.test.js | 5 +++++ test/relations.integration.js | 5 +++++ test/remote-connector.test.js | 5 +++++ test/remoting-coercion.test.js | 5 +++++ test/remoting.integration.js | 5 +++++ test/replication.rest.test.js | 5 +++++ test/replication.test.js | 5 +++++ test/rest.middleware.test.js | 5 +++++ test/role.test.js | 5 +++++ test/support.js | 5 +++++ test/user.integration.js | 5 +++++ test/user.test.js | 5 +++++ test/util/describe.js | 5 +++++ test/util/it.js | 5 +++++ test/util/model-tests.js | 5 +++++ 105 files changed, 525 insertions(+) diff --git a/Gruntfile.js b/Gruntfile.js index 3df919962..756bede3a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*global module:false*/ module.exports = function(grunt) { diff --git a/browser/current-context.js b/browser/current-context.js index cdf1d8a28..97d4a1a70 100644 --- a/browser/current-context.js +++ b/browser/current-context.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(loopback) { loopback.getCurrentContext = function() { return null; diff --git a/common/models/access-token.js b/common/models/access-token.js index 27cf5206d..750c21f8b 100644 --- a/common/models/access-token.js +++ b/common/models/access-token.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Module Dependencies. */ diff --git a/common/models/acl.js b/common/models/acl.js index 2a7306b17..d4823cec8 100644 --- a/common/models/acl.js +++ b/common/models/acl.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! Schema ACL options diff --git a/common/models/application.js b/common/models/application.js index 617798c15..3286410a5 100644 --- a/common/models/application.js +++ b/common/models/application.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var assert = require('assert'); var utils = require('../../lib/utils'); diff --git a/common/models/change.js b/common/models/change.js index 3b347ec5c..605caa23d 100644 --- a/common/models/change.js +++ b/common/models/change.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Module Dependencies. */ diff --git a/common/models/checkpoint.js b/common/models/checkpoint.js index 59cc33de0..b48cb1866 100644 --- a/common/models/checkpoint.js +++ b/common/models/checkpoint.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /** * Module Dependencies. */ diff --git a/common/models/email.js b/common/models/email.js index f73628027..6a6736dc6 100644 --- a/common/models/email.js +++ b/common/models/email.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /** * Email model. Extends LoopBack base [Model](#model-new-model). * @property {String} to Email addressee. Required. diff --git a/common/models/role-mapping.js b/common/models/role-mapping.js index ee728483c..53af71f9a 100644 --- a/common/models/role-mapping.js +++ b/common/models/role-mapping.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../lib/loopback'); /** diff --git a/common/models/role.js b/common/models/role.js index 523e68982..c86049b74 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../lib/loopback'); var debug = require('debug')('loopback:security:role'); var assert = require('assert'); diff --git a/common/models/scope.js b/common/models/scope.js index 3c713b535..478124d2a 100644 --- a/common/models/scope.js +++ b/common/models/scope.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var assert = require('assert'); var loopback = require('../../lib/loopback'); diff --git a/common/models/user.js b/common/models/user.js index b91b3ca18..7466ca8e7 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Module Dependencies. */ diff --git a/example/client-server/client.js b/example/client-server/client.js index 436e266b8..48c098f5e 100644 --- a/example/client-server/client.js +++ b/example/client-server/client.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../'); var client = loopback(); var CartItem = require('./models').CartItem; diff --git a/example/client-server/models.js b/example/client-server/models.js index 34d5c8bac..ac1b14320 100644 --- a/example/client-server/models.js +++ b/example/client-server/models.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../'); var CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', { diff --git a/example/client-server/server.js b/example/client-server/server.js index 7e466a563..52c738d3a 100644 --- a/example/client-server/server.js +++ b/example/client-server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../'); var server = module.exports = loopback(); var CartItem = require('./models').CartItem; diff --git a/example/colors/app.js b/example/colors/app.js index e182f926b..3e57b373b 100644 --- a/example/colors/app.js +++ b/example/colors/app.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../'); var app = loopback(); diff --git a/example/context/app.js b/example/context/app.js index 12cedc078..fa35eacca 100644 --- a/example/context/app.js +++ b/example/context/app.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../'); var app = loopback(); diff --git a/example/mobile-models/app.js b/example/mobile-models/app.js index e7e3c582b..abdf34c15 100644 --- a/example/mobile-models/app.js +++ b/example/mobile-models/app.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var models = require('../../lib/models'); var loopback = require('../../'); diff --git a/example/replication/app.js b/example/replication/app.js index ab6e69870..32e32299a 100644 --- a/example/replication/app.js +++ b/example/replication/app.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../'); var app = loopback(); var db = app.dataSource('db', {connector: loopback.Memory}); diff --git a/example/simple-data-source/app.js b/example/simple-data-source/app.js index 3964df77b..234baeb05 100644 --- a/example/simple-data-source/app.js +++ b/example/simple-data-source/app.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../'); var app = loopback(); diff --git a/index.js b/index.js index 1512239a7..bd558efa0 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /** * loopback ~ public api */ diff --git a/lib/access-context.js b/lib/access-context.js index 75ec50165..af04bdbba 100644 --- a/lib/access-context.js +++ b/lib/access-context.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var assert = require('assert'); var loopback = require('./loopback'); var debug = require('debug')('loopback:security:access-context'); diff --git a/lib/application.js b/lib/application.js index c5485fdb1..ae8ebcf36 100644 --- a/lib/application.js +++ b/lib/application.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Module dependencies. */ diff --git a/lib/browser-express.js b/lib/browser-express.js index 2a7dbe912..3b4237202 100644 --- a/lib/browser-express.js +++ b/lib/browser-express.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var EventEmitter = require('events').EventEmitter; var util = require('util'); diff --git a/lib/builtin-models.js b/lib/builtin-models.js index 78bf63990..59f0ff655 100644 --- a/lib/builtin-models.js +++ b/lib/builtin-models.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(registry) { // NOTE(bajtos) we must use static require() due to browserify limitations diff --git a/lib/connectors/base-connector.js b/lib/connectors/base-connector.js index c1e37b7ba..a11dcce31 100644 --- a/lib/connectors/base-connector.js +++ b/lib/connectors/base-connector.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /** * Expose `Connector`. */ diff --git a/lib/connectors/mail.js b/lib/connectors/mail.js index a36984c39..3271c145e 100644 --- a/lib/connectors/mail.js +++ b/lib/connectors/mail.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /** * Dependencies. */ diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index 6a34417cd..f62448f61 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /** * Expose `Memory`. */ diff --git a/lib/express-middleware.js b/lib/express-middleware.js index f058a74a6..cc7596ff7 100644 --- a/lib/express-middleware.js +++ b/lib/express-middleware.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var path = require('path'); var middlewares = exports; diff --git a/lib/loopback.js b/lib/loopback.js index fd770e20d..656db41c2 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Module dependencies. */ diff --git a/lib/model.js b/lib/model.js index 685e84045..5ffd82fb5 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Module Dependencies. */ diff --git a/lib/persisted-model.js b/lib/persisted-model.js index 5dceeec04..734992795 100644 --- a/lib/persisted-model.js +++ b/lib/persisted-model.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Module Dependencies. */ diff --git a/lib/registry.js b/lib/registry.js index ade0e2e88..ccce6248f 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var assert = require('assert'); var extend = require('util')._extend; var juggler = require('loopback-datasource-juggler'); diff --git a/lib/runtime.js b/lib/runtime.js index 7e791f5b1..8799447bb 100644 --- a/lib/runtime.js +++ b/lib/runtime.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /* * This is an internal file that should not be used outside of loopback. * All exported entities can be accessed via the `loopback` object. diff --git a/lib/server-app.js b/lib/server-app.js index 237a62540..290c9b5ac 100644 --- a/lib/server-app.js +++ b/lib/server-app.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var assert = require('assert'); var express = require('express'); var merge = require('util')._extend; diff --git a/lib/utils.js b/lib/utils.js index 306a1764a..555a18616 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + exports.createPromiseCallback = createPromiseCallback; function createPromiseCallback() { diff --git a/server/current-context.js b/server/current-context.js index 6b8304e22..4da00bf4c 100644 --- a/server/current-context.js +++ b/server/current-context.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var juggler = require('loopback-datasource-juggler'); var remoting = require('strong-remoting'); var cls = require('continuation-local-storage'); diff --git a/server/middleware/context.js b/server/middleware/context.js index 95352018f..73948bd76 100644 --- a/server/middleware/context.js +++ b/server/middleware/context.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../lib/loopback'); module.exports = context; diff --git a/server/middleware/error-handler.js b/server/middleware/error-handler.js index c549944bf..1d30ae289 100644 --- a/server/middleware/error-handler.js +++ b/server/middleware/error-handler.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var expressErrorHandler = require('errorhandler'); expressErrorHandler.title = 'Loopback'; diff --git a/server/middleware/favicon.js b/server/middleware/favicon.js index d2e1fa40d..b5cf10cec 100644 --- a/server/middleware/favicon.js +++ b/server/middleware/favicon.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /** * Serve the LoopBack favicon. * @header loopback.favicon() diff --git a/server/middleware/rest.js b/server/middleware/rest.js index 9c7e23a28..2ec5a200d 100644 --- a/server/middleware/rest.js +++ b/server/middleware/rest.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Module dependencies. */ diff --git a/server/middleware/static.js b/server/middleware/static.js index c01a538df..6f253dfa5 100644 --- a/server/middleware/static.js +++ b/server/middleware/static.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /** * Serve static assets of a LoopBack application. * diff --git a/server/middleware/status.js b/server/middleware/status.js index 3e9308115..f064a9d83 100644 --- a/server/middleware/status.js +++ b/server/middleware/status.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Export the middleware. */ diff --git a/server/middleware/token.js b/server/middleware/token.js index 146c75d17..b5038df23 100644 --- a/server/middleware/token.js +++ b/server/middleware/token.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Module dependencies. */ diff --git a/server/middleware/url-not-found.js b/server/middleware/url-not-found.js index dd696d79f..d204f1575 100644 --- a/server/middleware/url-not-found.js +++ b/server/middleware/url-not-found.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*! * Export the middleware. * See discussion in Connect pull request #954 for more details diff --git a/test/access-control.integration.js b/test/access-control.integration.js index 7e417bfe3..eed48a227 100644 --- a/test/access-control.integration.js +++ b/test/access-control.integration.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*jshint -W030 */ var loopback = require('../'); diff --git a/test/access-token.test.js b/test/access-token.test.js index a2ddbdb09..b6f8bce06 100644 --- a/test/access-token.test.js +++ b/test/access-token.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../'); var extend = require('util')._extend; var Token = loopback.AccessToken.extend('MyToken'); diff --git a/test/acl.test.js b/test/acl.test.js index d8706eec3..4bb182cb0 100644 --- a/test/acl.test.js +++ b/test/acl.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var assert = require('assert'); var loopback = require('../index'); var Scope = loopback.Scope; diff --git a/test/app.test.js b/test/app.test.js index 347d738b7..8b0d0d52c 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*jshint -W030 */ var async = require('async'); diff --git a/test/change-stream.test.js b/test/change-stream.test.js index ab7405214..177e31904 100644 --- a/test/change-stream.test.js +++ b/test/change-stream.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + describe('PersistedModel.createChangeStream()', function() { describe('configured to source changes locally', function() { before(function() { diff --git a/test/change.test.js b/test/change.test.js index 2f43c037f..5adc87589 100644 --- a/test/change.test.js +++ b/test/change.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var async = require('async'); var expect = require('chai').expect; diff --git a/test/checkpoint.test.js b/test/checkpoint.test.js index c824d02ea..ca7272ea2 100644 --- a/test/checkpoint.test.js +++ b/test/checkpoint.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var async = require('async'); var loopback = require('../'); var expect = require('chai').expect; diff --git a/test/data-source.test.js b/test/data-source.test.js index 662c07184..19ce7a87e 100644 --- a/test/data-source.test.js +++ b/test/data-source.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + describe('DataSource', function() { var memory; diff --git a/test/e2e/remote-connector.e2e.js b/test/e2e/remote-connector.e2e.js index 1a3424718..6a49536c2 100644 --- a/test/e2e/remote-connector.e2e.js +++ b/test/e2e/remote-connector.e2e.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var path = require('path'); var loopback = require('../../'); var models = require('../fixtures/e2e/models'); diff --git a/test/e2e/replication.e2e.js b/test/e2e/replication.e2e.js index 24f6967e0..74671f66c 100644 --- a/test/e2e/replication.e2e.js +++ b/test/e2e/replication.e2e.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var path = require('path'); var loopback = require('../../'); var models = require('../fixtures/e2e/models'); diff --git a/test/email.test.js b/test/email.test.js index 018f543ca..a5be75314 100644 --- a/test/email.test.js +++ b/test/email.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../'); var MyEmail; var assert = require('assert'); diff --git a/test/error-handler.test.js b/test/error-handler.test.js index 0494f2ef3..f5a1a6629 100644 --- a/test/error-handler.test.js +++ b/test/error-handler.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../'); var app; var assert = require('assert'); diff --git a/test/fixtures/access-control/server/server.js b/test/fixtures/access-control/server/server.js index ef251648a..13c2b9100 100644 --- a/test/fixtures/access-control/server/server.js +++ b/test/fixtures/access-control/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../../..'); var boot = require('loopback-boot'); var app = module.exports = loopback(); diff --git a/test/fixtures/e2e/server/models.js b/test/fixtures/e2e/server/models.js index dc36ca030..4b658610c 100644 --- a/test/fixtures/e2e/server/models.js +++ b/test/fixtures/e2e/server/models.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../../../index'); var PersistedModel = loopback.PersistedModel; diff --git a/test/fixtures/e2e/server/server.js b/test/fixtures/e2e/server/server.js index bd8a9411d..7fd6b7452 100644 --- a/test/fixtures/e2e/server/server.js +++ b/test/fixtures/e2e/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../../../index'); var app = module.exports = loopback(); var models = require('./models'); diff --git a/test/fixtures/shared-methods/both-configs-set/common/models/todo.js b/test/fixtures/shared-methods/both-configs-set/common/models/todo.js index 43ab55fbb..5d5125b83 100644 --- a/test/fixtures/shared-methods/both-configs-set/common/models/todo.js +++ b/test/fixtures/shared-methods/both-configs-set/common/models/todo.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Todo) { }; diff --git a/test/fixtures/shared-methods/both-configs-set/server/server.js b/test/fixtures/shared-methods/both-configs-set/server/server.js index 7876752e9..0d3e6b52e 100644 --- a/test/fixtures/shared-methods/both-configs-set/server/server.js +++ b/test/fixtures/shared-methods/both-configs-set/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var boot = require('loopback-boot'); var loopback = require('../../../../../index'); diff --git a/test/fixtures/shared-methods/config-default-false/common/models/todo.js b/test/fixtures/shared-methods/config-default-false/common/models/todo.js index 43ab55fbb..5d5125b83 100644 --- a/test/fixtures/shared-methods/config-default-false/common/models/todo.js +++ b/test/fixtures/shared-methods/config-default-false/common/models/todo.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Todo) { }; diff --git a/test/fixtures/shared-methods/config-default-false/server/server.js b/test/fixtures/shared-methods/config-default-false/server/server.js index 7876752e9..0d3e6b52e 100644 --- a/test/fixtures/shared-methods/config-default-false/server/server.js +++ b/test/fixtures/shared-methods/config-default-false/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var boot = require('loopback-boot'); var loopback = require('../../../../../index'); diff --git a/test/fixtures/shared-methods/config-default-true/common/models/todo.js b/test/fixtures/shared-methods/config-default-true/common/models/todo.js index 43ab55fbb..5d5125b83 100644 --- a/test/fixtures/shared-methods/config-default-true/common/models/todo.js +++ b/test/fixtures/shared-methods/config-default-true/common/models/todo.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Todo) { }; diff --git a/test/fixtures/shared-methods/config-default-true/server/server.js b/test/fixtures/shared-methods/config-default-true/server/server.js index 7876752e9..0d3e6b52e 100644 --- a/test/fixtures/shared-methods/config-default-true/server/server.js +++ b/test/fixtures/shared-methods/config-default-true/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var boot = require('loopback-boot'); var loopback = require('../../../../../index'); diff --git a/test/fixtures/shared-methods/config-defined-false/common/models/todo.js b/test/fixtures/shared-methods/config-defined-false/common/models/todo.js index 43ab55fbb..5d5125b83 100644 --- a/test/fixtures/shared-methods/config-defined-false/common/models/todo.js +++ b/test/fixtures/shared-methods/config-defined-false/common/models/todo.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Todo) { }; diff --git a/test/fixtures/shared-methods/config-defined-false/server/server.js b/test/fixtures/shared-methods/config-defined-false/server/server.js index 7876752e9..0d3e6b52e 100644 --- a/test/fixtures/shared-methods/config-defined-false/server/server.js +++ b/test/fixtures/shared-methods/config-defined-false/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var boot = require('loopback-boot'); var loopback = require('../../../../../index'); diff --git a/test/fixtures/shared-methods/config-defined-true/common/models/todo.js b/test/fixtures/shared-methods/config-defined-true/common/models/todo.js index 43ab55fbb..5d5125b83 100644 --- a/test/fixtures/shared-methods/config-defined-true/common/models/todo.js +++ b/test/fixtures/shared-methods/config-defined-true/common/models/todo.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Todo) { }; diff --git a/test/fixtures/shared-methods/config-defined-true/server/server.js b/test/fixtures/shared-methods/config-defined-true/server/server.js index 7876752e9..0d3e6b52e 100644 --- a/test/fixtures/shared-methods/config-defined-true/server/server.js +++ b/test/fixtures/shared-methods/config-defined-true/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var boot = require('loopback-boot'); var loopback = require('../../../../../index'); diff --git a/test/fixtures/shared-methods/model-config-default-false/common/models/todo.js b/test/fixtures/shared-methods/model-config-default-false/common/models/todo.js index 43ab55fbb..5d5125b83 100644 --- a/test/fixtures/shared-methods/model-config-default-false/common/models/todo.js +++ b/test/fixtures/shared-methods/model-config-default-false/common/models/todo.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Todo) { }; diff --git a/test/fixtures/shared-methods/model-config-default-false/server/server.js b/test/fixtures/shared-methods/model-config-default-false/server/server.js index 7876752e9..0d3e6b52e 100644 --- a/test/fixtures/shared-methods/model-config-default-false/server/server.js +++ b/test/fixtures/shared-methods/model-config-default-false/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var boot = require('loopback-boot'); var loopback = require('../../../../../index'); diff --git a/test/fixtures/shared-methods/model-config-default-true/common/models/todo.js b/test/fixtures/shared-methods/model-config-default-true/common/models/todo.js index 43ab55fbb..5d5125b83 100644 --- a/test/fixtures/shared-methods/model-config-default-true/common/models/todo.js +++ b/test/fixtures/shared-methods/model-config-default-true/common/models/todo.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Todo) { }; diff --git a/test/fixtures/shared-methods/model-config-default-true/server/server.js b/test/fixtures/shared-methods/model-config-default-true/server/server.js index 7876752e9..0d3e6b52e 100644 --- a/test/fixtures/shared-methods/model-config-default-true/server/server.js +++ b/test/fixtures/shared-methods/model-config-default-true/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var boot = require('loopback-boot'); var loopback = require('../../../../../index'); diff --git a/test/fixtures/shared-methods/model-config-defined-false/common/models/todo.js b/test/fixtures/shared-methods/model-config-defined-false/common/models/todo.js index 43ab55fbb..5d5125b83 100644 --- a/test/fixtures/shared-methods/model-config-defined-false/common/models/todo.js +++ b/test/fixtures/shared-methods/model-config-defined-false/common/models/todo.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Todo) { }; diff --git a/test/fixtures/shared-methods/model-config-defined-false/server/server.js b/test/fixtures/shared-methods/model-config-defined-false/server/server.js index 7876752e9..0d3e6b52e 100644 --- a/test/fixtures/shared-methods/model-config-defined-false/server/server.js +++ b/test/fixtures/shared-methods/model-config-defined-false/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var boot = require('loopback-boot'); var loopback = require('../../../../../index'); diff --git a/test/fixtures/shared-methods/model-config-defined-true/common/models/todo.js b/test/fixtures/shared-methods/model-config-defined-true/common/models/todo.js index 43ab55fbb..5d5125b83 100644 --- a/test/fixtures/shared-methods/model-config-defined-true/common/models/todo.js +++ b/test/fixtures/shared-methods/model-config-defined-true/common/models/todo.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Todo) { }; diff --git a/test/fixtures/shared-methods/model-config-defined-true/server/server.js b/test/fixtures/shared-methods/model-config-defined-true/server/server.js index 7876752e9..0d3e6b52e 100644 --- a/test/fixtures/shared-methods/model-config-defined-true/server/server.js +++ b/test/fixtures/shared-methods/model-config-defined-true/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var boot = require('loopback-boot'); var loopback = require('../../../../../index'); diff --git a/test/fixtures/simple-app/boot/foo.js b/test/fixtures/simple-app/boot/foo.js index 7e7486341..3e779699b 100644 --- a/test/fixtures/simple-app/boot/foo.js +++ b/test/fixtures/simple-app/boot/foo.js @@ -1 +1,6 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + process.loadedFooJS = true; diff --git a/test/fixtures/simple-app/common/models/bar.js b/test/fixtures/simple-app/common/models/bar.js index 10a3d968f..2c4064c85 100644 --- a/test/fixtures/simple-app/common/models/bar.js +++ b/test/fixtures/simple-app/common/models/bar.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + module.exports = function(Bar) { process.loadedBarJS = true; }; diff --git a/test/fixtures/simple-integration-app/server/server.js b/test/fixtures/simple-integration-app/server/server.js index d3f1c09c8..7c92b9c2a 100644 --- a/test/fixtures/simple-integration-app/server/server.js +++ b/test/fixtures/simple-integration-app/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../../../index'); var boot = require('loopback-boot'); var app = module.exports = loopback(); diff --git a/test/fixtures/user-integration-app/server/server.js b/test/fixtures/user-integration-app/server/server.js index 1d3d6720f..bbcb7fcc3 100644 --- a/test/fixtures/user-integration-app/server/server.js +++ b/test/fixtures/user-integration-app/server/server.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../../../index'); var boot = require('loopback-boot'); var app = module.exports = loopback(); diff --git a/test/geo-point.test.js b/test/geo-point.test.js index 9372caaea..8fb84b3e6 100644 --- a/test/geo-point.test.js +++ b/test/geo-point.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + describe('GeoPoint', function() { describe('geoPoint.distanceTo(geoPoint, options)', function() { it('Get the distance to another `GeoPoint`', function() { diff --git a/test/hidden-properties.test.js b/test/hidden-properties.test.js index c5e80e9ae..c00f538f5 100644 --- a/test/hidden-properties.test.js +++ b/test/hidden-properties.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../'); describe('hidden properties', function() { diff --git a/test/integration.test.js b/test/integration.test.js index 7aae5dc0b..27a7317cc 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var net = require('net'); describe('loopback application', function() { it('pauses request stream during authentication', function(done) { diff --git a/test/karma.conf.js b/test/karma.conf.js index f77923674..96a4d4e6d 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + // Karma configuration // http://karma-runner.github.io/0.12/config/configuration-file.html diff --git a/test/loopback.test.js b/test/loopback.test.js index 63d61d140..5531e8951 100644 --- a/test/loopback.test.js +++ b/test/loopback.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var it = require('./util/it'); var describe = require('./util/describe'); var Domain = require('domain'); diff --git a/test/memory.test.js b/test/memory.test.js index 7d9feb70e..4fe2f2a7e 100644 --- a/test/memory.test.js +++ b/test/memory.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + describe('Memory Connector', function() { it('Create a model using the memory connector', function(done) { // use the built in memory function diff --git a/test/model.application.test.js b/test/model.application.test.js index 96d8ab756..088172627 100644 --- a/test/model.application.test.js +++ b/test/model.application.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require(('../')); var assert = require('assert'); var Application = loopback.Application; diff --git a/test/model.test.js b/test/model.test.js index 37147f40a..46ec17eea 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var async = require('async'); var chai = require('chai'); var expect = chai.expect; diff --git a/test/registries.test.js b/test/registries.test.js index 8f44ac426..28804d043 100644 --- a/test/registries.test.js +++ b/test/registries.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + describe('Registry', function() { describe('one per app', function() { it('should allow two apps to reuse the same model name', function(done) { diff --git a/test/relations.integration.js b/test/relations.integration.js index dbb2e2556..5a1068f41 100644 --- a/test/relations.integration.js +++ b/test/relations.integration.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*jshint -W030 */ var loopback = require('../'); diff --git a/test/remote-connector.test.js b/test/remote-connector.test.js index e267c2e86..62a499f76 100644 --- a/test/remote-connector.test.js +++ b/test/remote-connector.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../'); var defineModelTestsWithDataSource = require('./util/model-tests'); diff --git a/test/remoting-coercion.test.js b/test/remoting-coercion.test.js index 6dab48e08..ee7aa51ef 100644 --- a/test/remoting-coercion.test.js +++ b/test/remoting-coercion.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../'); var request = require('supertest'); diff --git a/test/remoting.integration.js b/test/remoting.integration.js index b2b92cd81..02c255384 100644 --- a/test/remoting.integration.js +++ b/test/remoting.integration.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../'); var lt = require('loopback-testing'); var path = require('path'); diff --git a/test/replication.rest.test.js b/test/replication.rest.test.js index 6316457f0..68a79595e 100644 --- a/test/replication.rest.test.js +++ b/test/replication.rest.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var async = require('async'); var debug = require('debug')('test'); var extend = require('util')._extend; diff --git a/test/replication.test.js b/test/replication.test.js index ecdffbf8d..df6ca7874 100644 --- a/test/replication.test.js +++ b/test/replication.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var assert = require('assert'); var async = require('async'); var loopback = require('../'); diff --git a/test/rest.middleware.test.js b/test/rest.middleware.test.js index 61b935644..614209049 100644 --- a/test/rest.middleware.test.js +++ b/test/rest.middleware.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var path = require('path'); describe('loopback.rest', function() { diff --git a/test/role.test.js b/test/role.test.js index 8e5d1a129..7ac313bb7 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var assert = require('assert'); var sinon = require('sinon'); var loopback = require('../index'); diff --git a/test/support.js b/test/support.js index 9e2d791f0..915f38dc4 100644 --- a/test/support.js +++ b/test/support.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /** * loopback test setup and support. */ diff --git a/test/user.integration.js b/test/user.integration.js index fd4a198bc..b3a82cd56 100644 --- a/test/user.integration.js +++ b/test/user.integration.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2015,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + /*jshint -W030 */ var loopback = require('../'); var lt = require('loopback-testing'); diff --git a/test/user.test.js b/test/user.test.js index 23fca2ad5..e4839c6e2 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2013,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + require('./support'); var loopback = require('../'); var User, AccessToken; diff --git a/test/util/describe.js b/test/util/describe.js index db7121131..ebcc3555c 100644 --- a/test/util/describe.js +++ b/test/util/describe.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../'); module.exports = describe; diff --git a/test/util/it.js b/test/util/it.js index f1b004e24..e3316dffe 100644 --- a/test/util/it.js +++ b/test/util/it.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var loopback = require('../../'); module.exports = it; diff --git a/test/util/model-tests.js b/test/util/model-tests.js index cd89f307b..d5509ca38 100644 --- a/test/util/model-tests.js +++ b/test/util/model-tests.js @@ -1,3 +1,8 @@ +// Copyright IBM Corp. 2014,2016. All Rights Reserved. +// Node module: loopback +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + var async = require('async'); var describe = require('./describe'); var loopback = require('../../'); From 4798b2f8c9b2416e435f8b478bd8b8008df28b26 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Fri, 29 Apr 2016 15:50:11 +0700 Subject: [PATCH 30/35] Add feature to not allow duplicate role name - Also fix jshint error in backported test --- common/models/role.js | 2 ++ test/role.test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/common/models/role.js b/common/models/role.js index c86049b74..b8c1242d5 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -445,4 +445,6 @@ module.exports = function(Role) { if (callback) callback(err, roles); }); }; + + Role.validatesUniquenessOf('name', { message: 'already exists' }); }; diff --git a/test/role.test.js b/test/role.test.js index 7ac313bb7..d11f4b376 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -93,6 +93,22 @@ describe('role model', function() { }); + it('should not allow duplicate role name', function(done) { + Role.create({ name: 'userRole' }, function(err, role) { + if (err) return done(err); + + Role.create({ name: 'userRole' }, function(err, role) { + expect(err).to.exist; //jshint ignore:line + expect(err).to.have.property('name', 'ValidationError'); + expect(err).to.have.deep.property('details.codes.name'); + expect(err.details.codes.name).to.contain('uniqueness'); + expect(err).to.have.property('statusCode', 422); + + done(); + }); + }); + }); + it('should automatically generate role id', function() { User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) { @@ -233,6 +249,7 @@ describe('role model', function() { password: 'jpass' }, function(err, u) { if (err) return done(err); + user = u; User.create({ username: 'mary', @@ -240,15 +257,18 @@ describe('role model', function() { password: 'mpass' }, function(err, u) { if (err) return done(err); + Application.create({ name: 'demo' }, function(err, a) { if (err) return done(err); + app = a; Role.create({ name: 'admin' }, function(err, r) { if (err) return done(err); + role = r; var principals = [ { @@ -272,7 +292,9 @@ describe('role model', function() { it('should resolve user by id', function(done) { ACL.resolvePrincipal(ACL.USER, user.id, function(err, u) { if (err) return done(err); + expect(u.id).to.eql(user.id); + done(); }); }); @@ -280,7 +302,9 @@ describe('role model', function() { it('should resolve user by username', function(done) { ACL.resolvePrincipal(ACL.USER, user.username, function(err, u) { if (err) return done(err); + expect(u.username).to.eql(user.username); + done(); }); }); @@ -288,7 +312,9 @@ describe('role model', function() { it('should resolve user by email', function(done) { ACL.resolvePrincipal(ACL.USER, user.email, function(err, u) { if (err) return done(err); + expect(u.email).to.eql(user.email); + done(); }); }); @@ -296,7 +322,9 @@ describe('role model', function() { it('should resolve app by id', function(done) { ACL.resolvePrincipal(ACL.APP, app.id, function(err, a) { if (err) return done(err); + expect(a.id).to.eql(app.id); + done(); }); }); @@ -304,7 +332,9 @@ describe('role model', function() { it('should resolve app by name', function(done) { ACL.resolvePrincipal(ACL.APP, app.name, function(err, a) { if (err) return done(err); + expect(a.name).to.eql(app.name); + done(); }); }); @@ -312,7 +342,9 @@ describe('role model', function() { it('should report isMappedToRole by user.username', function(done) { ACL.isMappedToRole(ACL.USER, user.username, 'admin', function(err, flag) { if (err) return done(err); + expect(flag).to.eql(true); + done(); }); }); @@ -320,7 +352,9 @@ describe('role model', function() { it('should report isMappedToRole by user.email', function(done) { ACL.isMappedToRole(ACL.USER, user.email, 'admin', function(err, flag) { if (err) return done(err); + expect(flag).to.eql(true); + done(); }); }); @@ -329,7 +363,9 @@ describe('role model', function() { function(done) { ACL.isMappedToRole(ACL.USER, 'mary', 'admin', function(err, flag) { if (err) return done(err); + expect(flag).to.eql(false); + done(); }); }); @@ -337,7 +373,9 @@ describe('role model', function() { it('should report isMappedToRole by app.name', function(done) { ACL.isMappedToRole(ACL.APP, app.name, 'admin', function(err, flag) { if (err) return done(err); + expect(flag).to.eql(true); + done(); }); }); @@ -345,7 +383,9 @@ describe('role model', function() { it('should report isMappedToRole by app.name', function(done) { ACL.isMappedToRole(ACL.APP, app.name, 'admin', function(err, flag) { if (err) return done(err); + expect(flag).to.eql(true); + done(); }); }); @@ -383,6 +423,7 @@ describe('role model', function() { role[pluralName](function(err, models) { assert(!err); assert.equal(models.length, 1); + if (++runs === mappings.length) { done(); } @@ -404,6 +445,7 @@ describe('role model', function() { assert.equal(users.length, 1); assert.equal(users[0].id, user.id); assert(User.find.calledWith(query)); + done(); }); }); From e89fbd7ce835d1bfae17bd9afff2115d61a82b08 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Tue, 3 May 2016 09:45:21 +0700 Subject: [PATCH 31/35] Clean up by removing unnecessary comments Signed-off-by: Supasate Choochaisri --- test/role.test.js | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/test/role.test.js b/test/role.test.js index d11f4b376..e2de4f928 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -15,7 +15,6 @@ var async = require('async'); var expect = require('chai').expect; function checkResult(err, result) { - // console.log(err, result); assert(!err); } @@ -65,11 +64,10 @@ describe('role model', function() { }); it('should define role/user relations', function() { - - User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) { - // console.log('User: ', user.id); - Role.create({name: 'userRole'}, function(err, role) { - role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) { + User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { + Role.create({ name: 'userRole' }, function(err, role) { + role.principals.create({ principalType: RoleMapping.USER, principalId: user.id }, + function(err, p) { Role.find(function(err, roles) { assert(!err); assert.equal(roles.length, 1); @@ -77,7 +75,6 @@ describe('role model', function() { }); role.principals(function(err, principals) { assert(!err); - // console.log(principals); assert.equal(principals.length, 1); assert.equal(principals[0].principalType, RoleMapping.USER); assert.equal(principals[0].principalId, user.id); @@ -110,10 +107,8 @@ describe('role model', function() { }); it('should automatically generate role id', function() { - - User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) { - // console.log('User: ', user.id); - Role.create({name: 'userRole'}, function(err, role) { + User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { + Role.create({ name: 'userRole' }, function(err, role) { assert(role.id); role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) { assert(p.id); @@ -125,7 +120,6 @@ describe('role model', function() { }); role.principals(function(err, principals) { assert(!err); - // console.log(principals); assert.equal(principals.length, 1); assert.equal(principals[0].principalType, RoleMapping.USER); assert.equal(principals[0].principalId, user.id); @@ -142,13 +136,12 @@ describe('role model', function() { }); it('should support getRoles() and isInRole()', function() { - User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) { - // console.log('User: ', user.id); - Role.create({name: 'userRole'}, function(err, role) { - role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) { - // Role.find(console.log); - // role.principals(console.log); - Role.isInRole('userRole', {principalType: RoleMapping.USER, principalId: user.id}, function(err, exists) { + User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { + Role.create({ name: 'userRole' }, function(err, role) { + role.principals.create({ principalType: RoleMapping.USER, principalId: user.id }, + function(err, p) { + Role.isInRole('userRole', { principalType: RoleMapping.USER, principalId: user.id }, + function(err, exists) { assert(!err && exists === true); }); @@ -225,9 +218,9 @@ describe('role model', function() { assert(!err && yes); }); - // console.log('User: ', user.id); - Album.create({name: 'Album 1', userId: user.id}, function(err, album1) { - Role.isInRole(Role.OWNER, {principalType: ACL.USER, principalId: user.id, model: Album, id: album1.id}, function(err, yes) { + Album.create({ name: 'Album 1', userId: user.id }, function(err, album1) { + var role = { principalType: ACL.USER, principalId: user.id, model: Album, id: album1.id }; + Role.isInRole(Role.OWNER, role, function(err, yes) { assert(!err && yes); }); Album.create({name: 'Album 2'}, function(err, album2) { From 25ade96d27ad96dedfc21b4313f6c7eb2477f406 Mon Sep 17 00:00:00 2001 From: Simon Ho Date: Fri, 6 May 2016 13:50:01 -0700 Subject: [PATCH 32/35] Backport separate error checking and done logic --- test/access-control.integration.js | 2 + test/access-token.test.js | 36 ++++- test/acl.test.js | 3 + test/app.test.js | 61 ++++++++ test/change-stream.test.js | 3 + test/change.test.js | 39 ++++++ test/checkpoint.test.js | 9 ++ test/e2e/remote-connector.e2e.js | 4 + test/e2e/replication.e2e.js | 4 +- test/email.test.js | 2 + test/error-handler.test.js | 3 + test/hidden-properties.test.js | 4 + test/integration.test.js | 2 + test/loopback.test.js | 4 + test/memory.test.js | 1 + test/model.application.test.js | 26 +++- test/model.test.js | 39 +++++- test/registries.test.js | 1 + test/relations.integration.js | 167 +++++++++++++++++++--- test/remote-connector.test.js | 4 + test/remoting-coercion.test.js | 2 + test/remoting.integration.js | 2 + test/replication.rest.test.js | 50 +++++++ test/replication.test.js | 108 +++++++++++++- test/rest.middleware.test.js | 31 ++++- test/role.test.js | 2 + test/user.integration.js | 43 +++--- test/user.test.js | 217 +++++++++++++++++------------ test/util/model-tests.js | 14 +- 29 files changed, 737 insertions(+), 146 deletions(-) diff --git a/test/access-control.integration.js b/test/access-control.integration.js index eed48a227..b32152573 100644 --- a/test/access-control.integration.js +++ b/test/access-control.integration.js @@ -137,6 +137,7 @@ describe('access control - integration', function() { var userCounter; function newUserData() { userCounter = userCounter ? ++userCounter : 1; + return { email: 'new-' + userCounter + '@test.test', password: 'test' @@ -214,6 +215,7 @@ describe('access control - integration', function() { balance: 100 }, function(err, act) { self.url = '/api/accounts/' + act.id; + done(); }); diff --git a/test/access-token.test.js b/test/access-token.test.js index b6f8bce06..573cae5a4 100644 --- a/test/access-token.test.js +++ b/test/access-token.test.js @@ -149,7 +149,8 @@ describe('loopback.token(options)', function() { .set('authorization', id) .end(function(err, res) { assert(!err); - assert.deepEqual(res.body, {userId: userId}); + assert.deepEqual(res.body, { userId: userId }); + done(); }); }); @@ -164,7 +165,8 @@ describe('loopback.token(options)', function() { .set('authorization', id) .end(function(err, res) { assert(!err); - assert.deepEqual(res.body, {userId: userId, state: 1}); + assert.deepEqual(res.body, { userId: userId, state: 1 }); + done(); }); }); @@ -179,7 +181,8 @@ describe('loopback.token(options)', function() { .set('authorization', id) .end(function(err, res) { assert(!err); - assert.deepEqual(res.body, {userId: userId, state: 1}); + assert.deepEqual(res.body, { userId: userId, state: 1 }); + done(); }); }); @@ -188,6 +191,7 @@ describe('loopback.token(options)', function() { var tokenStub = { id: 'stub id' }; app.use(function(req, res, next) { req.accessToken = tokenStub; + next(); }); app.use(loopback.token({ model: Token })); @@ -200,7 +204,9 @@ describe('loopback.token(options)', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body).to.eql(tokenStub); + done(); }); }); @@ -211,6 +217,7 @@ describe('loopback.token(options)', function() { var tokenStub = { id: 'stub id' }; app.use(function(req, res, next) { req.accessToken = tokenStub; + next(); }); app.use(loopback.token({ model: Token })); @@ -223,7 +230,9 @@ describe('loopback.token(options)', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body).to.eql(tokenStub); + done(); }); }); @@ -234,6 +243,7 @@ describe('loopback.token(options)', function() { var tokenStub = { id: 'stub id' }; app.use(function(req, res, next) { req.accessToken = tokenStub; + next(); }); app.use(loopback.token({ @@ -249,7 +259,9 @@ describe('loopback.token(options)', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body).to.eql(tokenStub); + done(); }); }); @@ -262,6 +274,7 @@ describe('loopback.token(options)', function() { app.use(function(req, res, next) { req.accessToken = tokenStub; + next(); }); app.use(loopback.token({ @@ -278,12 +291,14 @@ describe('loopback.token(options)', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body).to.eql({ id: token.id, ttl: token.ttl, userId: token.userId, created: token.created.toJSON(), }); + done(); }); }); @@ -306,6 +321,7 @@ describe('AccessToken', function() { it('should be validateable', function(done) { this.token.validate(function(err, isValid) { assert(isValid); + done(); }); }); @@ -321,7 +337,9 @@ describe('AccessToken', function() { Token.findForRequest(req, function(err, token) { if (err) return done(err); + expect(token.id).to.eql(expectedTokenId); + done(); }); }); @@ -355,9 +373,11 @@ describe('app.enableAuth()', function() { if (err) { return done(err); } + var errorResponse = res.body.error; assert(errorResponse); assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED'); + done(); }); }); @@ -371,9 +391,11 @@ describe('app.enableAuth()', function() { if (err) { return done(err); } + var errorResponse = res.body.error; assert(errorResponse); assert.equal(errorResponse.code, 'ACCESS_DENIED'); + done(); }); }); @@ -387,9 +409,11 @@ describe('app.enableAuth()', function() { if (err) { return done(err); } + var errorResponse = res.body.error; assert(errorResponse); assert.equal(errorResponse.code, 'MODEL_NOT_FOUND'); + done(); }); }); @@ -403,9 +427,11 @@ describe('app.enableAuth()', function() { if (err) { return done(err); } + var errorResponse = res.body.error; assert(errorResponse); assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED'); + done(); }); }); @@ -436,7 +462,9 @@ describe('app.enableAuth()', function() { .expect('Content-Type', /json/) .end(function(err, res) { if (err) return done(err); + expect(res.body.token.id).to.eql(token.id); + done(); }); }); @@ -446,7 +474,9 @@ function createTestingToken(done) { var test = this; Token.create({userId: '123'}, function(err, token) { if (err) return done(err); + test.token = token; + done(); }); } diff --git a/test/acl.test.js b/test/acl.test.js index 4bb182cb0..3022ebe6b 100644 --- a/test/acl.test.js +++ b/test/acl.test.js @@ -387,7 +387,9 @@ describe('access check', function() { MyTestModel.beforeRemote('find', function(ctx, next) { // ensure this is called after checkAccess if (!checkAccessCalled) return done(new Error('incorrect order')); + beforeHookCalled = true; + next(); }); @@ -396,6 +398,7 @@ describe('access check', function() { .end(function(err, result) { assert(beforeHookCalled, 'the before hook should be called'); assert(checkAccessCalled, 'checkAccess should have been called'); + done(); }); }); diff --git a/test/app.test.js b/test/app.test.js index 8b0d0d52c..fd8d03735 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -39,10 +39,12 @@ describe('app', function() { executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(steps).to.eql([ 'initial', 'session', 'auth', 'parse', 'main', 'routes', 'files', 'final' ]); + done(); }); }); @@ -53,7 +55,9 @@ describe('app', function() { executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(steps).to.eql(['first', 'second']); + done(); }); }); @@ -65,7 +69,9 @@ describe('app', function() { executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(steps).to.eql(['routes:before', 'main', 'routes:after']); + done(); }); }); @@ -85,7 +91,9 @@ describe('app', function() { expect(found).have.property('phase', 'routes:before'); executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(steps).to.eql(['my-handler', 'extra-handler']); + done(); }); }); @@ -103,7 +111,9 @@ describe('app', function() { expect(found).have.property('phase', 'routes:before'); executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(steps).to.eql(['my-handler']); + done(); }); }); @@ -121,7 +131,9 @@ describe('app', function() { expect(found).have.property('phase', 'routes:before'); executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(steps).to.eql(['my-handler']); + done(); }); }); @@ -131,6 +143,7 @@ describe('app', function() { app.middleware('initial', function(req, res, next) { steps.push('initial'); + next(expectedError); }); @@ -138,12 +151,15 @@ describe('app', function() { app.use(function errorHandler(err, req, res, next) { expect(err).to.equal(expectedError); steps.push('error'); + next(); }); executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(steps).to.eql(['initial', 'error']); + done(); }); }); @@ -157,6 +173,7 @@ describe('app', function() { executeMiddlewareHandlers(app, function(err) { expect(err).to.equal(expectedError); + done(); }); }); @@ -175,12 +192,15 @@ describe('app', function() { app.middleware('initial', function(err, req, res, next) { handledError = err; + next(); }); executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(handledError).to.equal(expectedError); + done(); }); }); @@ -193,7 +213,9 @@ describe('app', function() { function(url, next) { executeMiddlewareHandlers(app, url, next); }, function(err) { if (err) return done(err); + expect(steps).to.eql(['/scope', '/scope/item']); + done(); }); }); @@ -206,7 +228,9 @@ describe('app', function() { function(url, next) { executeMiddlewareHandlers(app, url, next); }, function(err) { if (err) return done(err); + expect(steps).to.eql(['/a', '/b']); + done(); }); }); @@ -219,7 +243,9 @@ describe('app', function() { function(url, next) { executeMiddlewareHandlers(app, url, next); }, function(err) { if (err) return done(err); + expect(steps).to.eql(['/a', '/b', '/scope']); + done(); }); }); @@ -227,12 +253,15 @@ describe('app', function() { it('sets req.url to a sub-path', function(done) { app.middleware('initial', ['/scope'], function(req, res, next) { steps.push(req.url); + next(); }); executeMiddlewareHandlers(app, '/scope/id', function(err) { if (err) return done(err); + expect(steps).to.eql(['/id']); + done(); }); }); @@ -244,11 +273,13 @@ describe('app', function() { app.middleware('initial', function(rq, rs, next) { req = rq; res = rs; + next(); }); executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(getObjectAndPrototypeKeys(req), 'request').to.include.members([ 'accepts', 'get', @@ -278,12 +309,15 @@ describe('app', function() { var reqProps; app.middleware('initial', function(req, res, next) { reqProps = { baseUrl: req.baseUrl, originalUrl: req.originalUrl }; + next(); }); executeMiddlewareHandlers(app, '/test/url', function(err) { if (err) return done(err); + expect(reqProps).to.eql({ baseUrl: '', originalUrl: '/test/url' }); + done(); }); }); @@ -295,7 +329,9 @@ describe('app', function() { executeMiddlewareHandlers(app, '/test', function(err) { if (err) return done(err); + expect(steps).to.eql(['route', 'files']); + done(); }); }); @@ -315,7 +351,9 @@ describe('app', function() { executeMiddlewareHandlers(app, function(err) { if (err) return done; + expect(steps).to.eql(numbers); + done(); }); }); @@ -329,6 +367,7 @@ describe('app', function() { mountpath: req.app.mountpath, parent: req.app.parent }; + next(); }); subapp.on('mount', function() { mountWasEmitted = true; }); @@ -337,11 +376,13 @@ describe('app', function() { executeMiddlewareHandlers(app, '/mountpath/test', function(err) { if (err) return done(err); + expect(mountWasEmitted, 'mountWasEmitted').to.be.true; expect(data).to.eql({ mountpath: '/mountpath', parent: app }); + done(); }); }); @@ -355,25 +396,30 @@ describe('app', function() { subapp.use(function verifyTestAssumptions(req, res, next) { expect(req.__proto__).to.not.equal(expected.req); expect(res.__proto__).to.not.equal(expected.res); + next(); }); app.middleware('initial', function saveOriginalValues(req, res, next) { expected.req = req.__proto__; expected.res = res.__proto__; + next(); }); app.middleware('routes', subapp); app.middleware('final', function saveActualValues(req, res, next) { actual.req = req.__proto__; actual.res = res.__proto__; + next(); }); executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(actual.req, 'req').to.equal(expected.req); expect(actual.res, 'res').to.equal(expected.res); + done(); }); }); @@ -388,6 +434,7 @@ describe('app', function() { function pathSavingHandler() { return function(req, res, next) { steps.push(req.originalUrl); + next(); }; } @@ -411,6 +458,7 @@ describe('app', function() { var args = Array.prototype.slice.apply(arguments); return function(req, res, next) { steps.push(args); + next(); }; }; @@ -461,12 +509,14 @@ describe('app', function() { executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(steps).to.eql([ ['before'], [expectedConfig], ['after', 2], [{x: 1}] ]); + done(); }); }); @@ -477,6 +527,7 @@ describe('app', function() { function factory() { return function(req, res, next) { steps.push(req.originalUrl); + next(); }; }, @@ -490,7 +541,9 @@ describe('app', function() { function(url, next) { executeMiddlewareHandlers(app, url, next); }, function(err) { if (err) return done(err); + expect(steps).to.eql(['/a', '/b', '/scope']); + done(); }); }); @@ -547,13 +600,16 @@ describe('app', function() { names.forEach(function(it) { app.middleware(it, function(req, res, next) { steps.push(it); + next(); }); }); executeMiddlewareHandlers(app, function(err) { if (err) return done(err); + expect(steps).to.eql(names); + done(); }); } @@ -786,6 +842,7 @@ describe('app', function() { app.listen(function() { expect(app.get('port'), 'port').to.not.equal(0); + done(); }); }); @@ -799,6 +856,7 @@ describe('app', function() { var host = process.platform === 'win32' ? 'localhost' : app.get('host'); var expectedUrl = 'http://' + host + ':' + app.get('port') + '/'; expect(app.get('url'), 'url').to.equal(expectedUrl); + done(); }); }); @@ -809,6 +867,7 @@ describe('app', function() { app.listen(0, '127.0.0.1', function() { expect(app.get('port'), 'port').to.not.equal(0).and.not.equal(1); expect(this.address().address).to.equal('127.0.0.1'); + done(); }); }); @@ -819,6 +878,7 @@ describe('app', function() { app.set('port', 1); app.listen(0).on('listening', function() { expect(app.get('port'), 'port') .to.not.equal(0).and.not.equal(1); + done(); }); } @@ -833,6 +893,7 @@ describe('app', function() { app.listen() .on('listening', function() { expect(this.address().address).to.equal('127.0.0.1'); + done(); }); }); diff --git a/test/change-stream.test.js b/test/change-stream.test.js index 177e31904..cbd24ca23 100644 --- a/test/change-stream.test.js +++ b/test/change-stream.test.js @@ -22,6 +22,7 @@ describe('PersistedModel.createChangeStream()', function() { changes.on('data', function(change) { expect(change.type).to.equal('create'); changes.destroy(); + done(); }); @@ -36,6 +37,7 @@ describe('PersistedModel.createChangeStream()', function() { changes.on('data', function(change) { expect(change.type).to.equal('update'); changes.destroy(); + done(); }); newScore.updateAttributes({ @@ -52,6 +54,7 @@ describe('PersistedModel.createChangeStream()', function() { changes.on('data', function(change) { expect(change.type).to.equal('remove'); changes.destroy(); + done(); }); diff --git a/test/change.test.js b/test/change.test.js index 5adc87589..220d8055f 100644 --- a/test/change.test.js +++ b/test/change.test.js @@ -33,9 +33,11 @@ describe('Change', function() { }; TestModel.create(test.data, function(err, model) { if (err) return done(err); + test.model = model; test.modelId = model.id; test.revisionForModel = Change.revisionForInst(model); + done(); }); }); @@ -66,6 +68,7 @@ describe('Change', function() { var test = this; Change.rectifyModelChanges(this.modelName, [this.modelId], function(err, trackedChanges) { if (err) return done(err); + done(); }); }); @@ -74,6 +77,7 @@ describe('Change', function() { var test = this; Change.find(function(err, trackedChanges) { assert.equal(trackedChanges[0].modelId, test.modelId.toString()); + done(); }); }); @@ -81,6 +85,7 @@ describe('Change', function() { it('should only create one change', function(done) { Change.count(function(err, count) { assert.equal(count, 1); + done(); }); }); @@ -103,6 +108,7 @@ describe('Change', function() { Change.find() .then(function(trackedChanges) { assert.equal(trackedChanges[0].modelId, test.modelId.toString()); + done(); }) .catch(done); @@ -112,6 +118,7 @@ describe('Change', function() { Change.count() .then(function(count) { assert.equal(count, 1); + done(); }) .catch(done); @@ -126,7 +133,9 @@ describe('Change', function() { var test = this; Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) { if (err) return done(err); + test.result = result; + done(); }); }); @@ -135,7 +144,9 @@ describe('Change', function() { var test = this; Change.findById(this.result.id, function(err, change) { if (err) return done(err); + assert.equal(change.id, test.result.id); + done(); }); }); @@ -147,6 +158,7 @@ describe('Change', function() { Change.findOrCreateChange(this.modelName, this.modelId) .then(function(result) { test.result = result; + done(); }) .catch(done); @@ -156,7 +168,9 @@ describe('Change', function() { var test = this; Change.findById(this.result.id, function(err, change) { if (err) return done(err); + assert.equal(change.id, test.result.id); + done(); }); }); @@ -170,6 +184,7 @@ describe('Change', function() { modelId: test.modelId }, function(err, change) { test.existingChange = change; + done(); }); }); @@ -178,7 +193,9 @@ describe('Change', function() { var test = this; Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) { if (err) return done(err); + test.result = result; + done(); }); }); @@ -186,6 +203,7 @@ describe('Change', function() { it('should find the entry', function(done) { var test = this; assert.equal(test.existingChange.id, test.result.id); + done(); }); }); @@ -201,6 +219,7 @@ describe('Change', function() { }, function(err, ch) { change = ch; + done(err); }); }); @@ -209,6 +228,7 @@ describe('Change', function() { var test = this; change.rectify(function(err, ch) { assert.equal(ch.rev, test.revisionForModel); + done(); }); }); @@ -232,6 +252,7 @@ describe('Change', function() { expect(change.type(), 'type').to.equal('update'); expect(change.prev, 'prev').to.equal(originalRev); expect(change.rev, 'rev').to.equal(test.revisionForModel); + next(); } ], done); @@ -243,7 +264,9 @@ describe('Change', function() { function checkpoint(next) { TestModel.checkpoint(function(err, inst) { if (err) return next(err); + cp = inst.seq; + next(); }); } @@ -254,6 +277,7 @@ describe('Change', function() { model.name += 'updated'; model.save(function(err) { test.revisionForModel = Change.revisionForInst(model); + next(err); }); } @@ -269,8 +293,10 @@ describe('Change', function() { change.rectify(function(err, c) { if (err) return done(err); + expect(c.rev, 'rev').to.equal(originalRev); // sanity check expect(c.checkpoint, 'checkpoint').to.equal(originalCheckpoint); + done(); }); }); @@ -283,6 +309,7 @@ describe('Change', function() { Change.findOrCreateChange(this.modelName, this.modelId) .then(function(ch) { change = ch; + done(); }) .catch(done); @@ -293,6 +320,7 @@ describe('Change', function() { change.rectify() .then(function(ch) { assert.equal(ch.rev, test.revisionForModel); + done(); }) .catch(done); @@ -309,6 +337,7 @@ describe('Change', function() { change.currentRevision(function(err, rev) { assert.equal(rev, test.revisionForModel); + done(); }); }); @@ -325,6 +354,7 @@ describe('Change', function() { change.currentRevision() .then(function(rev) { assert.equal(rev, test.revisionForModel); + done(); }) .catch(done); @@ -465,8 +495,10 @@ describe('Change', function() { Change.diff(this.modelName, 0, remoteChanges, function(err, diff) { if (err) return done(err); + assert.equal(diff.deltas.length, 1); assert.equal(diff.conflicts.length, 1); + done(); }); }); @@ -485,6 +517,7 @@ describe('Change', function() { .then(function(diff) { assert.equal(diff.deltas.length, 1); assert.equal(diff.conflicts.length, 1); + done(); }) .catch(done); @@ -500,6 +533,7 @@ describe('Change', function() { }; Change.diff(this.modelName, 0, [updateRecord], function(err, diff) { if (err) return done(err); + expect(diff.conflicts, 'conflicts').to.have.length(0); expect(diff.deltas, 'deltas').to.have.length(1); var actual = diff.deltas[0].toObject(); @@ -511,6 +545,7 @@ describe('Change', function() { prev: 'foo', // this is the current local revision rev: 'foo-new', }); + done(); }); }); @@ -527,6 +562,7 @@ describe('Change', function() { // with rev=foo CP=1 Change.diff(this.modelName, 2, [updateRecord], function(err, diff) { if (err) return done(err); + expect(diff.conflicts, 'conflicts').to.have.length(0); expect(diff.deltas, 'deltas').to.have.length(1); var actual = diff.deltas[0].toObject(); @@ -538,6 +574,7 @@ describe('Change', function() { prev: 'foo', // this is the current local revision rev: 'foo-new', }); + done(); }); }); @@ -553,6 +590,7 @@ describe('Change', function() { Change.diff(this.modelName, 0, [updateRecord], function(err, diff) { if (err) return done(err); + expect(diff.conflicts).to.have.length(0); expect(diff.deltas).to.have.length(1); var actual = diff.deltas[0].toObject(); @@ -564,6 +602,7 @@ describe('Change', function() { prev: null, // this is the current local revision rev: 'new-rev', }); + done(); }); }); diff --git a/test/checkpoint.test.js b/test/checkpoint.test.js index ca7272ea2..b5b9093ae 100644 --- a/test/checkpoint.test.js +++ b/test/checkpoint.test.js @@ -25,7 +25,9 @@ describe('Checkpoint', function() { function(next) { Checkpoint.current(function(err, seq) { if (err) next(err); + expect(seq).to.equal(3); + next(); }); } @@ -38,9 +40,12 @@ describe('Checkpoint', function() { function(next) { Checkpoint.current(next); } ], function(err, list) { if (err) return done(err); + Checkpoint.find(function(err, data) { if (err) return done(err); + expect(data).to.have.length(1); + done(); }); }); @@ -52,6 +57,7 @@ describe('Checkpoint', function() { function(next) { Checkpoint.bumpLastSeq(next); } ], function(err, list) { if (err) return done(err); + Checkpoint.find(function(err, data) { if (err) return done(err); // The invariant "we have at most 1 checkpoint instance" is preserved @@ -64,6 +70,7 @@ describe('Checkpoint', function() { // should be 2. expect(list.map(function(it) {return it.seq;})) .to.eql([2, 2]); + done(); }); }); @@ -72,6 +79,7 @@ describe('Checkpoint', function() { it('Checkpoint.current() for non existing checkpoint should initialize checkpoint', function(done) { Checkpoint.current(function(err, seq) { expect(seq).to.equal(1); + done(err); }); }); @@ -82,6 +90,7 @@ describe('Checkpoint', function() { // `bumpLastSeq` for the first time not only initializes it to one, // but also increments the initialized value by one. expect(cp.seq).to.equal(2); + done(err); }); }); diff --git a/test/e2e/remote-connector.e2e.js b/test/e2e/remote-connector.e2e.js index 6a49536c2..e24436929 100644 --- a/test/e2e/remote-connector.e2e.js +++ b/test/e2e/remote-connector.e2e.js @@ -24,7 +24,9 @@ describe('RemoteConnector', function() { foo: 'bar' }, function(err, inst) { if (err) return done(err); + assert(inst.id); + done(); }); }); @@ -35,7 +37,9 @@ describe('RemoteConnector', function() { }); m.save(function(err, data) { if (err) return done(err); + assert(data.foo === 'bar'); + done(); }); }); diff --git a/test/e2e/replication.e2e.js b/test/e2e/replication.e2e.js index 74671f66c..ad7d57363 100644 --- a/test/e2e/replication.e2e.js +++ b/test/e2e/replication.e2e.js @@ -32,8 +32,10 @@ describe('Replication', function() { }, function(err, created) { LocalTestModel.replicate(0, TestModel, function() { if (err) return done(err); - TestModel.findOne({n: RANDOM}, function(err, found) { + + TestModel.findOne({ n: RANDOM }, function(err, found) { assert.equal(created.id, found.id); + done(); }); }); diff --git a/test/email.test.js b/test/email.test.js index a5be75314..be12a3a63 100644 --- a/test/email.test.js +++ b/test/email.test.js @@ -66,6 +66,7 @@ describe('Email and SMTP', function() { assert(mail.response); assert(mail.envelope); assert(mail.messageId); + done(err); }); }); @@ -83,6 +84,7 @@ describe('Email and SMTP', function() { assert(mail.response); assert(mail.envelope); assert(mail.messageId); + done(err); }); }); diff --git a/test/error-handler.test.js b/test/error-handler.test.js index f5a1a6629..273481240 100644 --- a/test/error-handler.test.js +++ b/test/error-handler.test.js @@ -22,6 +22,7 @@ describe('loopback.errorHandler(options)', function() { .get('/url-does-not-exist') .end(function(err, res) { assert.ok(res.error.text.match(/
  •    at raiseUrlNotFoundError/)); + done(); }); }); @@ -38,6 +39,7 @@ describe('loopback.errorHandler(options)', function() { .get('/url-does-not-exist') .end(function(err, res) { assert.ok(res.error.text.match(/
      <\/ul>/)); + done(); }); }); @@ -61,6 +63,7 @@ describe('loopback.errorHandler(options)', function() { //assert expect(errorLogged) .to.have.property('message', 'Cannot GET /url-does-not-exist'); + done(); }); diff --git a/test/hidden-properties.test.js b/test/hidden-properties.test.js index c00f538f5..6e3f72064 100644 --- a/test/hidden-properties.test.js +++ b/test/hidden-properties.test.js @@ -46,8 +46,10 @@ describe('hidden properties', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + var product = res.body[0]; assert.equal(product.secret, undefined); + done(); }); }); @@ -60,9 +62,11 @@ describe('hidden properties', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + var category = res.body[0]; var product = category.products[0]; assert.equal(product.secret, undefined); + done(); }); }); diff --git a/test/integration.test.js b/test/integration.test.js index 27a7317cc..074bd2625 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -21,7 +21,9 @@ describe('loopback application', function() { 'X', function(err, res) { if (err) return done(err); + expect(res).to.match(/\nX$/); + done(); }); }); diff --git a/test/loopback.test.js b/test/loopback.test.js index 5531e8951..b3aea149f 100644 --- a/test/loopback.test.js +++ b/test/loopback.test.js @@ -611,6 +611,7 @@ describe('loopback', function() { }else { ctxx.result.data = 'context not available'; } + next(); }); @@ -618,7 +619,9 @@ describe('loopback', function() { .get('/TestModels/test') .end(function(err, res) { if (err) return done(err); + expect(res.body.data).to.equal('a value stored in context'); + done(); }); }); @@ -632,6 +635,7 @@ describe('loopback', function() { var ctx = loopback.getCurrentContext(); expect(ctx).is.an('object'); expect(ctx.get('test-key')).to.equal('test-value'); + done(); }); }); diff --git a/test/memory.test.js b/test/memory.test.js index 4fe2f2a7e..9a223be5f 100644 --- a/test/memory.test.js +++ b/test/memory.test.js @@ -33,6 +33,7 @@ describe('Memory Connector', function() { function count() { Product.count(function(err, count) { assert.equal(count, 3); + done(); }); } diff --git a/test/model.application.test.js b/test/model.application.test.js index 088172627..2f6e17ca6 100644 --- a/test/model.application.test.js +++ b/test/model.application.test.js @@ -17,6 +17,7 @@ describe('Application', function() { assert.equal(app.owner, 'rfeng'); assert.equal(app.name, 'MyTestApp'); assert.equal(app.description, 'My test application'); + done(err, result); }); }); @@ -29,6 +30,7 @@ describe('Application', function() { assert.equal(app.owner, 'rfeng'); assert.equal(app.name, 'MyTestApp'); assert.equal(app.description, 'My test application'); + done(); }) .catch(function(err) { @@ -52,6 +54,7 @@ describe('Application', function() { assert(app.created); assert(app.modified); assert.equal(typeof app.id, 'string'); + done(err, result); }); }); @@ -102,6 +105,7 @@ describe('Application', function() { serverApiKey: 'serverKey' } }); + done(err, result); }); }); @@ -121,6 +125,7 @@ describe('Application', function() { assert(app.created); assert(app.modified); registeredApp = app; + done(err, result); }); }); @@ -146,6 +151,7 @@ describe('Application', function() { assert(app.created); assert(app.modified); registeredApp = app; + done(err, result); }); }); @@ -172,6 +178,7 @@ describe('Application', function() { assert(app.created); assert(app.modified); registeredApp = app; + done(); }) .catch(function(err) { @@ -185,6 +192,7 @@ describe('Application', function() { assert(app.id); assert(app.id === registeredApp.id); registeredApp = app; + done(err, result); }); }); @@ -196,6 +204,7 @@ describe('Application', function() { assert(app.id); assert(app.id === registeredApp.id); registeredApp = app; + done(); }) .catch(function(err) { @@ -208,6 +217,7 @@ describe('Application', function() { function(err, result) { assert.equal(result.application.id, registeredApp.id); assert.equal(result.keyType, 'clientKey'); + done(err, result); }); }); @@ -216,10 +226,11 @@ describe('Application', function() { function(done) { Application.authenticate(registeredApp.id, registeredApp.clientKey) .then(function(result) { - assert.equal(result.application.id, registeredApp.id); - assert.equal(result.keyType, 'clientKey'); - done(); - }) + assert.equal(result.application.id, registeredApp.id); + assert.equal(result.keyType, 'clientKey'); + + done(); + }) .catch(function(err) { done(err); }); @@ -230,6 +241,7 @@ describe('Application', function() { function(err, result) { assert.equal(result.application.id, registeredApp.id); assert.equal(result.keyType, 'javaScriptKey'); + done(err, result); }); }); @@ -239,6 +251,7 @@ describe('Application', function() { function(err, result) { assert.equal(result.application.id, registeredApp.id); assert.equal(result.keyType, 'restApiKey'); + done(err, result); }); }); @@ -248,6 +261,7 @@ describe('Application', function() { function(err, result) { assert.equal(result.application.id, registeredApp.id); assert.equal(result.keyType, 'masterKey'); + done(err, result); }); }); @@ -257,6 +271,7 @@ describe('Application', function() { function(err, result) { assert.equal(result.application.id, registeredApp.id); assert.equal(result.keyType, 'windowsKey'); + done(err, result); }); }); @@ -265,6 +280,7 @@ describe('Application', function() { Application.authenticate(registeredApp.id, 'invalid-key', function(err, result) { assert(!result); + done(err, result); }); }); @@ -273,6 +289,7 @@ describe('Application', function() { Application.authenticate(registeredApp.id, 'invalid-key') .then(function(result) { assert(!result); + done(); }) .catch(function(err) { @@ -309,6 +326,7 @@ describe('Application subclass', function() { Application.findById(app.id, function(err, myApp) { assert(!err); assert(myApp === null); + done(err, myApp); }); }); diff --git a/test/model.test.js b/test/model.test.js index 46ec17eea..a02690011 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -148,6 +148,7 @@ describe.onServer('Remote Methods', function() { User.destroyAll(function() { User.count(function(err, count) { assert.equal(count, 0); + done(); }); }); @@ -164,7 +165,9 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + assert.equal(res.body, 123); + done(); }); }); @@ -174,12 +177,12 @@ describe.onServer('Remote Methods', function() { .get('/users/not-found') .expect(404) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var errorResponse = res.body.error; assert(errorResponse); assert.equal(errorResponse.code, 'MODEL_NOT_FOUND'); + done(); }); }); @@ -192,6 +195,7 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + var userId = res.body.id; assert(userId); request(app) @@ -200,8 +204,10 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + assert.equal(res.body.first, 'x', 'first should be x'); assert(res.body.last === undefined, 'last should not be present'); + done(); }); }); @@ -215,6 +221,7 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + var userId = res.body.id; assert(userId); request(app) @@ -224,6 +231,7 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + var post = res.body; request(app) .get('/users/' + userId + '?filter[include]=posts') @@ -231,9 +239,11 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + assert.equal(res.body.first, 'x', 'first should be x'); assert.equal(res.body.last, 'y', 'last should be y'); assert.deepEqual(post, res.body.posts[0]); + done(); }); }); @@ -248,6 +258,7 @@ describe.onServer('Remote Methods', function() { User.beforeRemote('create', function(ctx, user, next) { hookCalled = true; + next(); }); @@ -259,7 +270,9 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + assert(hookCalled, 'hook wasnt called'); + done(); }); }); @@ -273,11 +286,13 @@ describe.onServer('Remote Methods', function() { User.beforeRemote('create', function(ctx, user, next) { assert(!afterCalled); beforeCalled = true; + next(); }); User.afterRemote('create', function(ctx, user, next) { assert(beforeCalled); afterCalled = true; + next(); }); @@ -289,8 +304,10 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + assert(beforeCalled, 'before hook was not called'); assert(afterCalled, 'after hook was not called'); + done(); }); }); @@ -301,14 +318,17 @@ describe.onServer('Remote Methods', function() { var actualError = 'hook not called'; User.afterRemoteError('login', function(ctx, next) { actualError = ctx.error; + next(); }); request(app).get('/users/sign-in?username=bob&password=123') .end(function(err, res) { if (err) return done(err); + expect(actualError) .to.have.property('message', 'bad username and password!'); + done(); }); }); @@ -327,6 +347,7 @@ describe.onServer('Remote Methods', function() { assert(ctx.res); assert(ctx.res.write); assert(ctx.res.end); + next(); }); @@ -338,7 +359,9 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + assert(hookCalled); + done(); }); }); @@ -356,6 +379,7 @@ describe.onServer('Remote Methods', function() { assert(ctx.res); assert(ctx.res.write); assert(ctx.res.end); + next(); }); @@ -367,7 +391,9 @@ describe.onServer('Remote Methods', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + assert(hookCalled); + done(); }); }); @@ -392,6 +418,7 @@ describe.onServer('Remote Methods', function() { book.chapters({where: {title: 'Chapter 1'}}, function(err, chapters) { assert.equal(chapters.length, 1); assert.equal(chapters[0].title, 'Chapter 1'); + done(); }); }); @@ -537,6 +564,7 @@ describe.onServer('Remote Methods', function() { it('Get the Source Id', function(done) { User.getSourceId(function(err, id) { assert.equal('memory-user', id); + done(); }); }); @@ -556,6 +584,7 @@ describe.onServer('Remote Methods', function() { if (err) return done(err); assert.equal(result, current + 1); + done(); }); @@ -655,7 +684,9 @@ describe.onServer('Remote Methods', function() { app.model(TestModel, { dataSource: 'db' }); TestModel.getApp(function(err, a) { if (err) return done(err); + expect(a).to.equal(app); + done(); }); // fails on time-out when not implemented correctly @@ -664,7 +695,9 @@ describe.onServer('Remote Methods', function() { it('calls the callback after attached', function(done) { TestModel.getApp(function(err, a) { if (err) return done(err); + expect(a).to.equal(app); + done(); }); app.model(TestModel, { dataSource: 'db' }); diff --git a/test/registries.test.js b/test/registries.test.js index 28804d043..2f9cda87f 100644 --- a/test/registries.test.js +++ b/test/registries.test.js @@ -43,6 +43,7 @@ describe('Registry', function() { expect(bars.map(function(f) { return f.parent; })).to.eql(['bar']); + done(); }); }); diff --git a/test/relations.integration.js b/test/relations.integration.js index 5a1068f41..e853513e8 100644 --- a/test/relations.integration.js +++ b/test/relations.integration.js @@ -79,10 +79,12 @@ describe('relations - integration', function() { app.models.Team.create({ name: 'Team 1' }, function(err, team) { if (err) return done(err); + test.team = team; app.models.Reader.create({ name: 'Reader 1' }, function(err, reader) { if (err) return done(err); + test.reader = reader; reader.pictures.create({ name: 'Picture 1' }); reader.pictures.create({ name: 'Picture 2' }); @@ -103,12 +105,13 @@ describe('relations - integration', function() { .query({'filter': {'include' : 'pictures'}}) .expect(200, function(err, res) { if (err) return done(err); - // console.log(res.body); + expect(res.body.name).to.be.equal('Reader 1'); expect(res.body.pictures).to.be.eql([ { name: 'Picture 1', id: 1, imageableId: 1, imageableType: 'Reader'}, { name: 'Picture 2', id: 2, imageableId: 1, imageableType: 'Reader'}, ]); + done(); }); }); @@ -119,10 +122,11 @@ describe('relations - integration', function() { .query({'filter': {'include' : 'imageable'}}) .expect(200, function(err, res) { if (err) return done(err); - // console.log(res.body); + expect(res.body[0].name).to.be.equal('Picture 1'); expect(res.body[1].name).to.be.equal('Picture 2'); - expect(res.body[0].imageable).to.be.eql({ name: 'Reader 1', id: 1, teamId: 1}); + expect(res.body[0].imageable).to.be.eql({ name: 'Reader 1', id: 1, teamId: 1 }); + done(); }); }); @@ -133,10 +137,12 @@ describe('relations - integration', function() { .query({'filter': {'include' : {'relation': 'imageable', 'scope': { 'include' : 'team'}}}}) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body[0].name).to.be.equal('Picture 1'); expect(res.body[1].name).to.be.equal('Picture 2'); expect(res.body[0].imageable.name).to.be.eql('Reader 1'); - expect(res.body[0].imageable.team).to.be.eql({ name: 'Team 1', id: 1}); + expect(res.body[0].imageable.team).to.be.eql({ name: 'Team 1', id: 1 }); + done(); }); }); @@ -148,7 +154,9 @@ describe('relations - integration', function() { this.get('/api/stores/superStores') .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.array; + done(); }); }); @@ -199,8 +207,10 @@ describe('relations - integration', function() { this.http.send(this.newWidget); this.http.end(function(err) { if (err) return done(err); + this.req = this.http.req; this.res = this.http.res; + done(); }.bind(this)); }); @@ -226,7 +236,9 @@ describe('relations - integration', function() { storeId: this.store.id }, function(err, count) { if (err) return done(err); + assert.equal(count, 2); + done(); }); }); @@ -241,6 +253,7 @@ describe('relations - integration', function() { }, function(err, widget) { self.widget = widget; self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id; + done(); }); }); @@ -314,6 +327,7 @@ describe('relations - integration', function() { }, function(err, widget) { self.widget = widget; self.url = '/api/widgets/' + self.widget.id + '/store'; + done(); }); }); @@ -348,6 +362,7 @@ describe('relations - integration', function() { name: 'ph1' }, function(err, physician) { root.physician = physician; + done(); }); }, @@ -360,6 +375,7 @@ describe('relations - integration', function() { root.patient = patient; root.relUrl = '/api/physicians/' + root.physician.id + '/patients/rel/' + root.patient.id; + done(); }); } : function(done) { @@ -369,6 +385,7 @@ describe('relations - integration', function() { root.patient = patient; root.relUrl = '/api/physicians/' + root.physician.id + '/patients/rel/' + root.patient.id; + done(); }); }], function(err, done) { @@ -384,6 +401,7 @@ describe('relations - integration', function() { self.url = root.relUrl; self.patient = root.patient; self.physician = root.physician; + done(err); }); }); @@ -400,6 +418,7 @@ describe('relations - integration', function() { app.models.appointment.find(function(err, apps) { assert.equal(apps.length, 1); assert.equal(apps[0].patientId, self.patient.id); + done(); }); }); @@ -409,6 +428,7 @@ describe('relations - integration', function() { self.physician.patients(function(err, patients) { assert.equal(patients.length, 1); assert.equal(patients[0].id, self.patient.id); + done(); }); }); @@ -423,6 +443,7 @@ describe('relations - integration', function() { self.url = root.relUrl; self.patient = root.patient; self.physician = root.physician; + done(err); }); }); @@ -445,6 +466,7 @@ describe('relations - integration', function() { assert.equal(apps[0].patientId, self.patient.id); assert.equal(apps[0].physicianId, self.physician.id); assert.equal(apps[0].date.getTime(), NOW); + done(); }); }); @@ -454,6 +476,7 @@ describe('relations - integration', function() { self.physician.patients(function(err, patients) { assert.equal(patients.length, 1); assert.equal(patients[0].id, self.patient.id); + done(); }); }); @@ -468,6 +491,7 @@ describe('relations - integration', function() { self.url = root.relUrl; self.patient = root.patient; self.physician = root.physician; + done(err); }); }); @@ -488,6 +512,7 @@ describe('relations - integration', function() { '/patients/rel/' + '999'; self.patient = root.patient; self.physician = root.physician; + done(err); }); }); @@ -507,6 +532,7 @@ describe('relations - integration', function() { self.url = root.relUrl; self.patient = root.patient; self.physician = root.physician; + done(err); }); }); @@ -516,6 +542,7 @@ describe('relations - integration', function() { app.models.appointment.find(function(err, apps) { assert.equal(apps.length, 1); assert.equal(apps[0].patientId, self.patient.id); + done(); }); }); @@ -525,6 +552,7 @@ describe('relations - integration', function() { self.physician.patients(function(err, patients) { assert.equal(patients.length, 1); assert.equal(patients[0].id, self.patient.id); + done(); }); }); @@ -538,6 +566,7 @@ describe('relations - integration', function() { var self = this; app.models.appointment.find(function(err, apps) { assert.equal(apps.length, 0); + done(); }); }); @@ -547,6 +576,7 @@ describe('relations - integration', function() { // Need to refresh the cache self.physician.patients(true, function(err, patients) { assert.equal(patients.length, 0); + done(); }); }); @@ -562,6 +592,7 @@ describe('relations - integration', function() { '/patients/' + root.patient.id; self.patient = root.patient; self.physician = root.physician; + done(err); }); }); @@ -583,6 +614,7 @@ describe('relations - integration', function() { '/patients/' + root.patient.id; self.patient = root.patient; self.physician = root.physician; + done(err); }); }); @@ -596,6 +628,7 @@ describe('relations - integration', function() { var self = this; app.models.appointment.find(function(err, apps) { assert.equal(apps.length, 0); + done(); }); }); @@ -605,6 +638,7 @@ describe('relations - integration', function() { // Need to refresh the cache self.physician.patients(true, function(err, patients) { assert.equal(patients.length, 0); + done(); }); }); @@ -613,6 +647,7 @@ describe('relations - integration', function() { var self = this; app.models.patient.find(function(err, patients) { assert.equal(patients.length, 0); + done(); }); }); @@ -644,7 +679,9 @@ describe('relations - integration', function() { name: 'a-product' }, function(err, product) { if (err) return done(err); + test.product = product; + done(); }); }); @@ -653,6 +690,7 @@ describe('relations - integration', function() { app.models.category.create({ name: 'another-category' }, function(err, cat) { if (err) return done(err); + cat.products.create({ name: 'another-product' }, done); }); }); @@ -667,12 +705,14 @@ describe('relations - integration', function() { this.get('/api/products?filter[where][categoryId]=' + this.category.id) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.eql([ { id: expectedProduct.id, name: expectedProduct.name } ]); + done(); }); }); @@ -682,12 +722,14 @@ describe('relations - integration', function() { this.get('/api/categories/' + this.category.id + '/products') .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.eql([ { id: expectedProduct.id, name: expectedProduct.name } ]); + done(); }); }); @@ -700,6 +742,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.have.property('products'); expect(res.body.products).to.eql([ { @@ -707,6 +750,7 @@ describe('relations - integration', function() { name: expectedProduct.name } ]); + done(); }); }); @@ -720,6 +764,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.have.property('products'); expect(res.body.products).to.eql([ { @@ -727,6 +772,7 @@ describe('relations - integration', function() { name: expectedProduct.name } ]); + done(); }); }); @@ -754,7 +800,9 @@ describe('relations - integration', function() { app.models.group.create({ name: 'Group 1' }, function(err, group) { if (err) return done(err); + test.group = group; + done(); }); }); @@ -772,6 +820,7 @@ describe('relations - integration', function() { expect(res.body).to.be.eql( { url: 'http://image.url' } ); + done(); }); }); @@ -782,10 +831,12 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body.name).to.be.equal('Group 1'); expect(res.body.poster).to.be.eql( { url: 'http://image.url' } ); + done(); }); }); @@ -796,9 +847,11 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql( { url: 'http://image.url' } ); + done(); }); }); @@ -810,6 +863,7 @@ describe('relations - integration', function() { .send({ url: 'http://changed.url' }) .expect(200, function(err, res) { expect(res.body.url).to.be.equal('http://changed.url'); + done(); }); }); @@ -820,9 +874,11 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql( { url: 'http://changed.url' } ); + done(); }); }); @@ -861,6 +917,7 @@ describe('relations - integration', function() { app.models.todoList.create({ name: 'List A' }, function(err, list) { if (err) return done(err); + test.todoList = list; list.items.build({ content: 'Todo 1' }); list.items.build({ content: 'Todo 2' }); @@ -878,11 +935,13 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body.name).to.be.equal('List A'); expect(res.body.todoItems).to.be.eql([ { content: 'Todo 1', id: 1 }, { content: 'Todo 2', id: 2 } ]); + done(); }); }); @@ -893,10 +952,12 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { content: 'Todo 1', id: 1 }, { content: 'Todo 2', id: 2 } ]); + done(); }); }); @@ -908,9 +969,11 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { content: 'Todo 2', id: 2 } ]); + done(); }); }); @@ -924,6 +987,7 @@ describe('relations - integration', function() { .send({ content: 'Todo 3' }) .expect(200, function(err, res) { expect(res.body).to.be.eql(expected); + done(); }); }); @@ -934,11 +998,13 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { content: 'Todo 1', id: 1 }, { content: 'Todo 2', id: 2 }, { content: 'Todo 3', id: 3 } ]); + done(); }); }); @@ -949,9 +1015,11 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql( { content: 'Todo 3', id: 3 } ); + done(); }); }); @@ -972,10 +1040,12 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { content: 'Todo 1', id: 1 }, { content: 'Todo 3', id: 3 } ]); + done(); }); }); @@ -984,9 +1054,11 @@ describe('relations - integration', function() { var url = '/api/todo-lists/' + this.todoList.id + '/items/2'; this.get(url).expect(404, function(err, res) { if (err) return done(err); + expect(res.body.error.status).to.be.equal(404); expect(res.body.error.message).to.be.equal('Unknown "todoItem" id "2".'); expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND'); + done(); }); }); @@ -1038,6 +1110,7 @@ describe('relations - integration', function() { app.models.recipe.create({ name: 'Recipe' }, function(err, recipe) { if (err) return done(err); + test.recipe = recipe; recipe.ingredients.create({ name: 'Chocolate' }, @@ -1052,6 +1125,7 @@ describe('relations - integration', function() { var test = this; app.models.ingredient.create({ name: 'Sugar' }, function(err, ing) { test.ingredient2 = ing.id; + done(); }); }); @@ -1072,8 +1146,10 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body.ingredientIds).to.eql([test.ingredient1]); expect(res.body).to.not.have.property('ingredients'); + done(); }); }); @@ -1087,6 +1163,7 @@ describe('relations - integration', function() { .expect(200, function(err, res) { expect(res.body.name).to.be.eql('Butter'); test.ingredient3 = res.body.id; + done(); }); }); @@ -1098,11 +1175,13 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Sugar', id: test.ingredient2 }, { name: 'Butter', id: test.ingredient3 } ]); + done(); }); }); @@ -1114,10 +1193,12 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Butter', id: test.ingredient3 } ]); + done(); }); }); @@ -1130,9 +1211,11 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { name: 'Butter', id: test.ingredient3 } ]); + done(); }); }); @@ -1145,6 +1228,7 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body.ingredientIds).to.eql([ test.ingredient1, test.ingredient3 ]); @@ -1152,6 +1236,7 @@ describe('relations - integration', function() { { name: 'Chocolate', id: test.ingredient1 }, { name: 'Butter', id: test.ingredient3 } ]); + done(); }); }); @@ -1164,9 +1249,11 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql( { name: 'Butter', id: test.ingredient3 } ); + done(); }); }); @@ -1180,8 +1267,10 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body.ingredientIds).to.eql(expected); expect(res.body).to.not.have.property('ingredients'); + done(); }); }); @@ -1204,10 +1293,12 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Sugar', id: test.ingredient2 } ]); + done(); }); }); @@ -1219,9 +1310,11 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 } ]); + done(); }); }); @@ -1236,6 +1329,7 @@ describe('relations - integration', function() { expect(res.body).to.be.eql( { name: 'Sugar', id: test.ingredient2 } ); + done(); }); }); @@ -1247,10 +1341,12 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Sugar', id: test.ingredient2 } ]); + done(); }); }); @@ -1273,9 +1369,11 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { name: 'Sugar', id: test.ingredient2 } ]); + done(); }); }); @@ -1287,10 +1385,12 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.eql([ { name: 'Chocolate', id: test.ingredient1 }, { name: 'Sugar', id: test.ingredient2 } ]); + done(); }); }); @@ -1301,8 +1401,10 @@ describe('relations - integration', function() { this.get(url) .expect(200, function(err, res) { if (err) return done(err); + expect(err).to.not.exist; expect(res.body.name).to.equal('Photo 1'); + done(); }); }); @@ -1379,11 +1481,13 @@ describe('relations - integration', function() { Page.beforeRemote('prototype.__findById__notes', function(ctx, result, next) { ctx.res.set('x-before', 'before'); + next(); }); Page.afterRemote('prototype.__findById__notes', function(ctx, result, next) { ctx.res.set('x-after', 'after'); + next(); }); @@ -1394,14 +1498,17 @@ describe('relations - integration', function() { app.models.Book.create({ name: 'Book 1' }, function(err, book) { if (err) return done(err); + test.book = book; book.pages.create({ name: 'Page 1' }, function(err, page) { if (err) return done(err); + test.page = page; page.notes.create({ text: 'Page Note 1' }, function(err, note) { test.note = note; + done(); }); }); @@ -1413,9 +1520,11 @@ describe('relations - integration', function() { test.book.chapters.create({ name: 'Chapter 1' }, function(err, chapter) { if (err) return done(err); + test.chapter = chapter; chapter.notes.create({ text: 'Chapter Note 1' }, function(err, note) { test.cnote = note; + done(); }); }); @@ -1426,7 +1535,9 @@ describe('relations - integration', function() { app.models.Image.create({ name: 'Cover 1', book: test.book }, function(err, image) { if (err) return done(err); + test.image = image; + done(); }); }); @@ -1436,9 +1547,11 @@ describe('relations - integration', function() { this.get('/api/books/' + test.book.id + '/pages') .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.an.array; expect(res.body).to.have.length(1); expect(res.body[0].name).to.equal('Page 1'); + done(); }); }); @@ -1448,10 +1561,12 @@ describe('relations - integration', function() { this.get('/api/pages/' + test.page.id + '/notes/' + test.note.id) .expect(200, function(err, res) { if (err) return done(err); + expect(res.headers['x-before']).to.equal('before'); expect(res.headers['x-after']).to.equal('after'); expect(res.body).to.be.an.object; expect(res.body.text).to.equal('Page Note 1'); + done(); }); }); @@ -1461,10 +1576,12 @@ describe('relations - integration', function() { this.get('/api/books/unknown/pages/' + test.page.id + '/notes') .expect(404, function(err, res) { if (err) return done(err); + expect(res.body.error).to.be.an.object; var expected = 'could not find a model with id unknown'; expect(res.body.error.message).to.equal(expected); expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND'); + done(); }); }); @@ -1474,9 +1591,11 @@ describe('relations - integration', function() { this.get('/api/images/' + test.image.id + '/book/pages') .end(function(err, res) { if (err) return done(err); + expect(res.body).to.be.an.array; expect(res.body).to.have.length(1); expect(res.body[0].name).to.equal('Page 1'); + done(); }); }); @@ -1486,8 +1605,10 @@ describe('relations - integration', function() { this.get('/api/images/' + test.image.id + '/book/pages/' + test.page.id) .end(function(err, res) { if (err) return done(err); + expect(res.body).to.be.an.object; expect(res.body.name).to.equal('Page 1'); + done(); }); }); @@ -1497,9 +1618,11 @@ describe('relations - integration', function() { this.get('/api/books/' + test.book.id + '/pages/' + test.page.id + '/notes') .expect(200, function(err, res) { if (err) return done(err); + expect(res.body).to.be.an.array; expect(res.body).to.have.length(1); expect(res.body[0].text).to.equal('Page Note 1'); + done(); }); }); @@ -1509,10 +1632,12 @@ describe('relations - integration', function() { this.get('/api/books/' + test.book.id + '/pages/' + test.page.id + '/notes/' + test.note.id) .expect(200, function(err, res) { if (err) return done(err); + expect(res.headers['x-before']).to.equal('before'); expect(res.headers['x-after']).to.equal('after'); expect(res.body).to.be.an.object; expect(res.body.text).to.equal('Page Note 1'); + done(); }); }); @@ -1522,8 +1647,10 @@ describe('relations - integration', function() { this.get('/api/books/' + test.book.id + '/chapters/' + test.chapter.id + '/notes/' + test.cnote.id) .expect(200, function(err, res) { if (err) return done(err); + expect(res.headers['x-before']).to.empty; expect(res.headers['x-after']).to.empty; + done(); }); }); @@ -1535,6 +1662,7 @@ describe('relations - integration', function() { http.forEach(function(opt) { // destroyAll has been shared but missing http property if (opt.path === undefined) return; + expect(opt.path, method.stringName).to.match(/^\/.*/); }); }); @@ -1546,11 +1674,13 @@ describe('relations - integration', function() { this.get('/api/books/' + test.book.id + '/pages/' + this.page.id + '/throws') .end(function(err, res) { if (err) return done(err); + expect(res.body).to.be.an('object'); expect(res.body.error).to.be.an('object'); expect(res.body.error.name).to.equal('Error'); expect(res.body.error.status).to.equal(500); expect(res.body.error.message).to.equal('This should not crash the app'); + done(); }); }); @@ -1562,10 +1692,10 @@ describe('relations - integration', function() { before(function createCustomer(done) { var test = this; app.models.customer.create({ name: 'John' }, function(err, c) { - if (err) { - return done(err); - } + if (err) return done(err); + cust = c; + done(); }); }); @@ -1573,9 +1703,8 @@ describe('relations - integration', function() { after(function(done) { var self = this; this.app.models.customer.destroyAll(function(err) { - if (err) { - return done(err); - } + if (err) return done(err); + self.app.models.profile.destroyAll(done); }); }); @@ -1586,11 +1715,11 @@ describe('relations - integration', function() { this.post(url) .send({points: 10}) .expect(200, function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + expect(res.body.points).to.be.eql(10); expect(res.body.customerId).to.be.eql(cust.id); + done(); }); }); @@ -1599,11 +1728,11 @@ describe('relations - integration', function() { var url = '/api/customers/' + cust.id + '/profile'; this.get(url) .expect(200, function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + expect(res.body.points).to.be.eql(10); expect(res.body.customerId).to.be.eql(cust.id); + done(); }); }); @@ -1622,11 +1751,11 @@ describe('relations - integration', function() { this.put(url) .send({points: 100}) .expect(200, function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + expect(res.body.points).to.be.eql(100); expect(res.body.customerId).to.be.eql(cust.id); + done(); }); }); diff --git a/test/remote-connector.test.js b/test/remote-connector.test.js index 62a499f76..701e170ba 100644 --- a/test/remote-connector.test.js +++ b/test/remote-connector.test.js @@ -21,6 +21,7 @@ describe('RemoteConnector', function() { port: remoteApp.get('port'), connector: loopback.Remote }); + done(); }); }, @@ -48,6 +49,7 @@ describe('RemoteConnector', function() { port: remoteApp.get('port'), connector: loopback.Remote }); + done(); }); }); @@ -70,8 +72,10 @@ describe('RemoteConnector', function() { var m = new RemoteModel({foo: 'bar'}); m.save(function(err, inst) { if (err) return done(err); + assert(inst instanceof RemoteModel); assert(calledServerCreate); + done(); }); }); diff --git a/test/remoting-coercion.test.js b/test/remoting-coercion.test.js index ee7aa51ef..bc6bd7950 100644 --- a/test/remoting-coercion.test.js +++ b/test/remoting-coercion.test.js @@ -32,7 +32,9 @@ describe('remoting coercion', function() { }) .end(function(err) { if (err) return done(err); + assert(called); + done(); }); }); diff --git a/test/remoting.integration.js b/test/remoting.integration.js index 02c255384..5154bb473 100644 --- a/test/remoting.integration.js +++ b/test/remoting.integration.js @@ -52,6 +52,7 @@ describe('remoting - integration', function() { this.req = this.http.req; this.res = this.http.res; assert.equal(this.res.statusCode, 200); + done(); }.bind(this)); }); @@ -72,6 +73,7 @@ describe('remoting - integration', function() { this.res = this.http.res; // Request is rejected with 413 assert.equal(this.res.statusCode, 413); + done(); }.bind(this)); }); diff --git a/test/replication.rest.test.js b/test/replication.rest.test.js index 68a79595e..eb963e7b7 100644 --- a/test/replication.rest.test.js +++ b/test/replication.rest.test.js @@ -114,11 +114,14 @@ describe('Replication over REST', function() { it('allows pull from server', function(done) { RemoteCar.replicate(LocalCar, function(err, conflicts, cps) { if (err) return done(err); + if (conflicts.length) return done(conflictError(conflicts)); LocalCar.find(function(err, list) { if (err) return done(err); + expect(list.map(carToString)).to.include.members(serverCars); + done(); }); }); @@ -137,11 +140,14 @@ describe('Replication over REST', function() { it('allows pull from server', function(done) { RemoteCar.replicate(LocalCar, function(err, conflicts, cps) { if (err) return done(err); + if (conflicts.length) return done(conflictError(conflicts)); LocalCar.find(function(err, list) { if (err) return done(err); + expect(list.map(carToString)).to.include.members(serverCars); + done(); }); }); @@ -150,11 +156,14 @@ describe('Replication over REST', function() { it('allows push to the server', function(done) { LocalCar.replicate(RemoteCar, function(err, conflicts, cps) { if (err) return done(err); + if (conflicts.length) return done(conflictError(conflicts)); ServerCar.find(function(err, list) { if (err) return done(err); + expect(list.map(carToString)).to.include.members(clientCars); + done(); }); }); @@ -224,6 +233,7 @@ describe('Replication over REST', function() { it('allows reverse resolve() on the client', function(done) { RemoteCar.replicate(LocalCar, function(err, conflicts) { if (err) return done(err); + expect(conflicts, 'conflicts').to.have.length(1); // By default, conflicts are always resolved by modifying @@ -238,7 +248,9 @@ describe('Replication over REST', function() { RemoteCar.replicate(LocalCar, function(err, conflicts) { if (err) return done(err); + if (conflicts.length) return done(conflictError(conflicts)); + done(); }); }); @@ -248,6 +260,7 @@ describe('Replication over REST', function() { it('rejects resolve() on the server', function(done) { RemoteCar.replicate(LocalCar, function(err, conflicts) { if (err) return done(err); + expect(conflicts, 'conflicts').to.have.length(1); conflicts[0].resolveUsingSource(expectHttpError(401, done)); }); @@ -262,13 +275,17 @@ describe('Replication over REST', function() { it('allows resolve() on the client', function(done) { LocalCar.replicate(RemoteCar, function(err, conflicts) { if (err) return done(err); + expect(conflicts).to.have.length(1); conflicts[0].resolveUsingSource(function(err) { if (err) return done(err); + LocalCar.replicate(RemoteCar, function(err, conflicts) { if (err) return done(err); + if (conflicts.length) return done(conflictError(conflicts)); + done(); }); }); @@ -278,13 +295,17 @@ describe('Replication over REST', function() { it('allows resolve() on the server', function(done) { RemoteCar.replicate(LocalCar, function(err, conflicts) { if (err) return done(err); + expect(conflicts).to.have.length(1); conflicts[0].resolveUsingSource(function(err) { if (err) return done(err); + RemoteCar.replicate(LocalCar, function(err, conflicts) { if (err) return done(err); + if (conflicts.length) return done(conflictError(conflicts)); + done(); }); }); @@ -298,10 +319,13 @@ describe('Replication over REST', function() { setAccessToken(aliceToken); RemoteUser.replicate(LocalUser, function(err, conflicts, cps) { if (err) return done(err); + if (conflicts.length) return done(conflictError(conflicts)); + LocalUser.find(function(err, users) { var userNames = users.map(function(u) { return u.username; }); expect(userNames).to.eql([ALICE.username]); + done(); }); }); @@ -315,7 +339,9 @@ describe('Replication over REST', function() { setAccessToken(aliceToken); LocalUser.replicate(RemoteUser, function(err, conflicts) { if (err) return next(err); + if (conflicts.length) return next(conflictError(conflicts)); + next(); }); }, @@ -323,8 +349,10 @@ describe('Replication over REST', function() { function verify(next) { RemoteUser.findById(aliceId, function(err, found) { if (err) return next(err); + expect(found.toObject()) .to.have.property('fullname', 'Alice Smith'); + next(); }); } @@ -340,7 +368,9 @@ describe('Replication over REST', function() { LocalUser.replicate(RemoteUser, function(err, conflicts) { if (!err) return next(new Error('Replicate should have failed.')); + expect(err).to.have.property('statusCode', 401); // or 403? + next(); }); }, @@ -348,8 +378,10 @@ describe('Replication over REST', function() { function verify(next) { ServerUser.findById(aliceId, function(err, found) { if (err) return next(err); + expect(found.toObject()) .to.not.have.property('fullname'); + next(); }); } @@ -461,6 +493,7 @@ describe('Replication over REST', function() { serverApp.use(function(req, res, next) { debug(req.method + ' ' + req.path); + next(); }); serverApp.use(loopback.token({ model: ServerToken })); @@ -472,6 +505,7 @@ describe('Replication over REST', function() { serverApp.listen(function() { serverUrl = serverApp.get('url').replace(/\/+$/, ''); request = supertest(serverUrl); + done(); }); } @@ -527,18 +561,22 @@ describe('Replication over REST', function() { function(next) { ServerUser.create([ALICE, PETER, EMERY], function(err, created) { if (err) return next(err); + aliceId = created[0].id; peterId = created[1].id; + next(); }); }, function(next) { ServerUser.login(ALICE, function(err, token) { if (err) return next(err); + aliceToken = token.id; ServerUser.login(PETER, function(err, token) { if (err) return next(err); + peterToken = token.id; ServerUser.login(EMERY, function(err, token) { @@ -557,7 +595,9 @@ describe('Replication over REST', function() { ], function(err, cars) { if (err) return next(err); + serverCars = cars.map(carToString); + next(); }); } @@ -574,7 +614,9 @@ describe('Replication over REST', function() { [{ maker: 'Local', model: 'Custom' }], function(err, cars) { if (err) return next(err); + clientCars = cars.map(carToString); + next(); }); }, @@ -584,9 +626,12 @@ describe('Replication over REST', function() { function seedConflict(done) { LocalCar.replicate(ServerCar, function(err, conflicts) { if (err) return done(err); + if (conflicts.length) return done(conflictError(conflicts)); + ServerCar.replicate(LocalCar, function(err, conflicts) { if (err) return done(err); + if (conflicts.length) return done(conflictError(conflicts)); // Hard-coded, see the seed data above @@ -595,6 +640,7 @@ describe('Replication over REST', function() { new LocalCar({ id: conflictedCarId }) .updateAttributes({ model: 'Client' }, function(err, c) { if (err) return done(err); + new ServerCar({ id: conflictedCarId }) .updateAttributes({ model: 'Server' }, done); }); @@ -612,7 +658,9 @@ describe('Replication over REST', function() { function expectHttpError(code, done) { return function(err) { if (!err) return done(new Error('The method should have failed.')); + expect(err).to.have.property('statusCode', code); + done(); }; } @@ -620,7 +668,9 @@ describe('Replication over REST', function() { function replicateServerToLocal(next) { ServerUser.replicate(LocalUser, function(err, conflicts) { if (err) return next(err); + if (conflicts.length) return next(conflictError(conflicts)); + next(); }); } diff --git a/test/replication.test.js b/test/replication.test.js index df6ca7874..83a88eb98 100644 --- a/test/replication.test.js +++ b/test/replication.test.js @@ -51,6 +51,7 @@ describe('Replication / Change APIs', function() { this.createInitalData = function(cb) { SourceModel.create({name: 'foo'}, function(err, inst) { if (err) return cb(err); + test.model = inst; SourceModel.replicate(TargetModel, cb); }); @@ -75,7 +76,9 @@ describe('Replication / Change APIs', function() { var calls = mockSourceModelRectify(); SourceModel.destroyAll({name: 'John'}, function(err, data) { if (err) return done(err); + expect(calls).to.eql(['rectifyAllChanges']); + done(); }); }); @@ -85,7 +88,9 @@ describe('Replication / Change APIs', function() { var newData = {'name': 'Janie'}; SourceModel.update({name: 'Jane'}, newData, function(err, data) { if (err) return done(err); + expect(calls).to.eql(['rectifyAllChanges']); + done(); }); }); @@ -102,7 +107,9 @@ describe('Replication / Change APIs', function() { } ], function(err, results) { if (err) return done(err); + expect(calls).to.eql(['rectifyChange']); + done(); }); }); @@ -120,7 +127,9 @@ describe('Replication / Change APIs', function() { } ], function(err, result) { if (err) return done(err); + expect(calls).to.eql(['rectifyChange']); + done(); }); }); @@ -138,7 +147,9 @@ describe('Replication / Change APIs', function() { } ], function(err, result) { if (err) return done(err); + expect(calls).to.eql(['rectifyChange']); + done(); }); }); @@ -181,9 +192,11 @@ describe('Replication / Change APIs', function() { var test = this; this.SourceModel.create({name: 'foo'}, function(err) { if (err) return done(err); + setTimeout(function() { test.SourceModel.changes(test.startingCheckpoint, {}, function(err, changes) { assert.equal(changes.length, 1); + done(); }); }, 1); @@ -197,8 +210,9 @@ describe('Replication / Change APIs', function() { if (err) return done(err); SourceModel.changes(FUTURE_CHECKPOINT, {}, function(err, changes) { if (err) return done(err); - /*jshint -W030 */ - expect(changes).to.be.empty; + + expect(changes).to.be.empty; //jshint ignore:line + done(); }); }); @@ -217,6 +231,7 @@ describe('Replication / Change APIs', function() { function(cb) { sourceModel.find(function(err, result) { if (err) return cb(err); + sourceData = result; cb(); }); @@ -224,6 +239,7 @@ describe('Replication / Change APIs', function() { function(cb) { targetModel.find(function(err, result) { if (err) return cb(err); + targetData = result; cb(); }); @@ -232,6 +248,7 @@ describe('Replication / Change APIs', function() { if (err) return done(err); assert.deepEqual(sourceData, targetData); + done(); }); } @@ -242,6 +259,7 @@ describe('Replication / Change APIs', function() { this.SourceModel.create({name: 'foo'}, function(err) { if (err) return done(err); + test.SourceModel.replicate(test.startingCheckpoint, test.TargetModel, options, function(err, conflicts) { if (err) return done(err); @@ -258,6 +276,7 @@ describe('Replication / Change APIs', function() { this.SourceModel.create({name: 'foo'}, function(err) { if (err) return done(err); + test.SourceModel.replicate(test.startingCheckpoint, test.TargetModel, options) .then(function(conflicts) { @@ -292,6 +311,7 @@ describe('Replication / Change APIs', function() { if (err) return done(err); // '1' should be skipped by replication expect(getIds(list)).to.eql(['2']); + next(); }); } @@ -324,6 +344,7 @@ describe('Replication / Change APIs', function() { if (err) return done(err); // '1' should be skipped by replication expect(getIds(list)).to.eql(['2']); + next(); }); } @@ -339,7 +360,9 @@ describe('Replication / Change APIs', function() { SourceModel.replicate(10, TargetModel, function(err) { if (err) return done(err); + expect(diffSince).to.eql([10]); + done(); }); }); @@ -354,6 +377,7 @@ describe('Replication / Change APIs', function() { SourceModel.replicate(10, TargetModel, {}) .then(function() { expect(diffSince).to.eql([10]); + done(); }) .catch(function(err) { @@ -371,8 +395,10 @@ describe('Replication / Change APIs', function() { var since = { source: 1, target: 2 }; SourceModel.replicate(since, TargetModel, function(err) { if (err) return done(err); + expect(sourceSince).to.eql([1]); expect(targetSince).to.eql([2]); + done(); }); }); @@ -389,6 +415,7 @@ describe('Replication / Change APIs', function() { .then(function() { expect(sourceSince).to.eql([1]); expect(targetSince).to.eql([2]); + done(); }) .catch(function(err) { @@ -411,7 +438,9 @@ describe('Replication / Change APIs', function() { function getLastCp(next) { SourceModel.currentCheckpoint(function(err, cp) { if (err) return done(err); + lastCp = cp; + next(); }); }, @@ -426,6 +455,7 @@ describe('Replication / Change APIs', function() { TargetModel.find(function(err, list) { expect(getIds(list), 'target ids after first sync') .to.include.members(['init']); + next(); }); }); @@ -436,6 +466,7 @@ describe('Replication / Change APIs', function() { function verify(next) { TargetModel.find(function(err, list) { expect(getIds(list), 'target ids').to.eql(['init', 'racer']); + next(); }); } @@ -455,11 +486,13 @@ describe('Replication / Change APIs', function() { TargetModel, function(err, conflicts, newCheckpoints) { if (err) return cb(err); + expect(conflicts, 'conflicts').to.eql([]); expect(newCheckpoints, 'currentCheckpoints').to.eql({ source: sourceCp + 1, target: targetCp + 1 }); + cb(); }); } @@ -468,7 +501,9 @@ describe('Replication / Change APIs', function() { function bumpSourceCheckpoint(cb) { SourceModel.checkpoint(function(err, inst) { if (err) return cb(err); + sourceCp = inst.seq; + cb(); }); } @@ -476,7 +511,9 @@ describe('Replication / Change APIs', function() { function bumpTargetCheckpoint(cb) { TargetModel.checkpoint(function(err, inst) { if (err) return cb(err); + targetCp = inst.seq; + cb(); }); } @@ -491,11 +528,14 @@ describe('Replication / Change APIs', function() { function verify(next) { TargetModel.currentCheckpoint(function(err, cp) { if (err) return next(err); + TargetModel.getChangeModel().find( { where: { checkpoint: { gte: cp } } }, function(err, changes) { if (err) return done(err); + expect(changes).to.have.length(0); + done(); }); }); @@ -659,6 +699,7 @@ describe('Replication / Change APIs', function() { cb); } }); + next(); }, replicateExpectingSuccess(), @@ -694,10 +735,13 @@ describe('Replication / Change APIs', function() { } ], function(err) { if (err) return done(err); + SourceModel.replicate(TargetModel, function(err, conflicts) { if (err) return done(err); + test.conflicts = conflicts; test.conflict = conflicts[0]; + done(); }); }); @@ -710,6 +754,7 @@ describe('Replication / Change APIs', function() { it('type should be UPDATE', function(done) { this.conflict.type(function(err, type) { assert.equal(type, Change.UPDATE); + done(); }); }); @@ -721,6 +766,7 @@ describe('Replication / Change APIs', function() { assert.equal(test.model.getId(), sourceChange.getModelId()); assert.equal(sourceChange.type(), Change.UPDATE); assert.equal(targetChange.type(), Change.UPDATE); + done(); }); }); @@ -735,6 +781,7 @@ describe('Replication / Change APIs', function() { id: test.model.id, name: 'target update' }); + done(); }); }); @@ -753,6 +800,7 @@ describe('Replication / Change APIs', function() { function(cb) { SourceModel.findOne(function(err, inst) { if (err) return cb(err); + test.model = inst; inst.remove(cb); }); @@ -760,16 +808,20 @@ describe('Replication / Change APIs', function() { function(cb) { TargetModel.findOne(function(err, inst) { if (err) return cb(err); + inst.name = 'target update'; inst.save(cb); }); } ], function(err) { if (err) return done(err); + SourceModel.replicate(TargetModel, function(err, conflicts) { if (err) return done(err); + test.conflicts = conflicts; test.conflict = conflicts[0]; + done(); }); }); @@ -782,6 +834,7 @@ describe('Replication / Change APIs', function() { it('type should be DELETE', function(done) { this.conflict.type(function(err, type) { assert.equal(type, Change.DELETE); + done(); }); }); @@ -793,6 +846,7 @@ describe('Replication / Change APIs', function() { assert.equal(test.model.getId(), sourceChange.getModelId()); assert.equal(sourceChange.type(), Change.DELETE); assert.equal(targetChange.type(), Change.UPDATE); + done(); }); }); @@ -804,6 +858,7 @@ describe('Replication / Change APIs', function() { id: test.model.id, name: 'target update' }); + done(); }); }); @@ -830,15 +885,19 @@ describe('Replication / Change APIs', function() { function(cb) { TargetModel.findOne(function(err, inst) { if (err) return cb(err); + inst.remove(cb); }); } ], function(err) { if (err) return done(err); + SourceModel.replicate(TargetModel, function(err, conflicts) { if (err) return done(err); + test.conflicts = conflicts; test.conflict = conflicts[0]; + done(); }); }); @@ -851,6 +910,7 @@ describe('Replication / Change APIs', function() { it('type should be DELETE', function(done) { this.conflict.type(function(err, type) { assert.equal(type, Change.DELETE); + done(); }); }); @@ -862,6 +922,7 @@ describe('Replication / Change APIs', function() { assert.equal(test.model.getId(), sourceChange.getModelId()); assert.equal(sourceChange.type(), Change.UPDATE); assert.equal(targetChange.type(), Change.DELETE); + done(); }); }); @@ -873,6 +934,7 @@ describe('Replication / Change APIs', function() { id: test.model.id, name: 'source update' }); + done(); }); }); @@ -891,6 +953,7 @@ describe('Replication / Change APIs', function() { function(cb) { SourceModel.findOne(function(err, inst) { if (err) return cb(err); + test.model = inst; inst.remove(cb); }); @@ -898,15 +961,19 @@ describe('Replication / Change APIs', function() { function(cb) { TargetModel.findOne(function(err, inst) { if (err) return cb(err); + inst.remove(cb); }); } ], function(err) { if (err) return done(err); + SourceModel.replicate(TargetModel, function(err, conflicts) { if (err) return done(err); + test.conflicts = conflicts; test.conflict = conflicts[0]; + done(); }); }); @@ -922,6 +989,7 @@ describe('Replication / Change APIs', function() { it('detects "create"', function(done) { SourceModel.create({}, function(err, inst) { if (err) return done(err); + assertChangeRecordedForId(inst.id, done); }); }); @@ -929,10 +997,12 @@ describe('Replication / Change APIs', function() { it('detects "updateOrCreate"', function(done) { givenReplicatedInstance(function(err, created) { if (err) return done(err); + var data = created.toObject(); created.name = 'updated'; SourceModel.updateOrCreate(created, function(err, inst) { if (err) return done(err); + assertChangeRecordedForId(inst.id, done); }); }); @@ -946,6 +1016,7 @@ describe('Replication / Change APIs', function() { this.all(model, query, function(err, list) { if (err || (list && list[0])) return callback(err, list && list[0], false); + this.create(model, data, function(err) { callback(err, data, true); }); @@ -955,6 +1026,7 @@ describe('Replication / Change APIs', function() { this.all(model, query, {}, function(err, list) { if (err || (list && list[0])) return callback(err, list && list[0], false); + this.create(model, data, {}, function(err) { callback(err, data, true); }); @@ -967,6 +1039,7 @@ describe('Replication / Change APIs', function() { { name: 'created' }, function(err, inst) { if (err) return done(err); + assertChangeRecordedForId(inst.id, done); }); }); @@ -974,6 +1047,7 @@ describe('Replication / Change APIs', function() { it('detects "deleteById"', function(done) { givenReplicatedInstance(function(err, inst) { if (err) return done(err); + SourceModel.deleteById(inst.id, function(err) { assertChangeRecordedForId(inst.id, done); }); @@ -983,8 +1057,10 @@ describe('Replication / Change APIs', function() { it('detects "deleteAll"', function(done) { givenReplicatedInstance(function(err, inst) { if (err) return done(err); + SourceModel.deleteAll({ name: inst.name }, function(err) { if (err) return done(err); + assertChangeRecordedForId(inst.id, done); }); }); @@ -993,11 +1069,13 @@ describe('Replication / Change APIs', function() { it('detects "updateAll"', function(done) { givenReplicatedInstance(function(err, inst) { if (err) return done(err); + SourceModel.updateAll( { name: inst.name }, { name: 'updated' }, function(err) { if (err) return done(err); + assertChangeRecordedForId(inst.id, done); }); }); @@ -1006,9 +1084,11 @@ describe('Replication / Change APIs', function() { it('detects "prototype.save"', function(done) { givenReplicatedInstance(function(err, inst) { if (err) return done(err); + inst.name = 'updated'; inst.save(function(err) { if (err) return done(err); + assertChangeRecordedForId(inst.id, done); }); }); @@ -1017,8 +1097,10 @@ describe('Replication / Change APIs', function() { it('detects "prototype.updateAttributes"', function(done) { givenReplicatedInstance(function(err, inst) { if (err) return done(err); + inst.updateAttributes({ name: 'updated' }, function(err) { if (err) return done(err); + assertChangeRecordedForId(inst.id, done); }); }); @@ -1027,6 +1109,7 @@ describe('Replication / Change APIs', function() { it('detects "prototype.delete"', function(done) { givenReplicatedInstance(function(err, inst) { if (err) return done(err); + inst.delete(function(err) { assertChangeRecordedForId(inst.id, done); }); @@ -1036,8 +1119,10 @@ describe('Replication / Change APIs', function() { function givenReplicatedInstance(cb) { SourceModel.create({ name: 'a-name' }, function(err, inst) { if (err) return cb(err); + SourceModel.checkpoint(function(err) { if (err) return cb(err); + cb(null, inst); }); }); @@ -1047,8 +1132,10 @@ describe('Replication / Change APIs', function() { SourceModel.getChangeModel().getCheckpointModel() .current(function(err, cp) { if (err) return cb(err); + SourceModel.changes(cp - 1, {}, function(err, pendingChanges) { if (err) return cb(err); + expect(pendingChanges, 'list of changes').to.have.length(1); var change = pendingChanges[0].toObject(); expect(change).to.have.property('checkpoint', cp); // sanity check @@ -1056,6 +1143,7 @@ describe('Replication / Change APIs', function() { // NOTE(bajtos) Change.modelId is always String // regardless of the type of the changed model's id property expect(change).to.have.property('modelId', '' + id); + cb(); }); }); @@ -1071,6 +1159,7 @@ describe('Replication / Change APIs', function() { SourceModel.create({ id: 'test-instance' }, function(err, result) { sourceInstance = result; sourceInstanceId = result.id; + next(err); }); }, @@ -1109,7 +1198,9 @@ describe('Replication / Change APIs', function() { function verifyTargetModelWasDeleted(next) { TargetModel.find(function(err, list) { if (err) return next(err); + expect(getIds(list)).to.not.contain(sourceInstance.id); + next(); }); } @@ -1376,6 +1467,7 @@ describe('Replication / Change APIs', function() { return function updateInstanceB(next) { ClientB.findById(sourceInstanceId, function(err, instance) { if (err) return next(err); + instance.name = name; instance.save(next); }); @@ -1408,6 +1500,7 @@ describe('Replication / Change APIs', function() { debug('delete source instance', value); sourceInstance.remove(function(err) { sourceInstance = null; + next(err); }); }; @@ -1415,11 +1508,14 @@ describe('Replication / Change APIs', function() { function verifySourceWasReplicated(target) { if (!target) target = TargetModel; + return function verify(next) { target.findById(sourceInstanceId, function(err, targetInstance) { if (err) return next(err); + expect(targetInstance && targetInstance.toObject()) .to.eql(sourceInstance && sourceInstance.toObject()); + next(); }); }; @@ -1445,9 +1541,11 @@ describe('Replication / Change APIs', function() { source.replicate(since, target, function(err, conflicts, cps) { if (err) return next(err); + if (conflicts.length === 0) { _since[sinceIx] = cps; } + next(err, conflicts, cps); }); } @@ -1465,10 +1563,12 @@ describe('Replication / Change APIs', function() { return function doReplicate(next) { replicate(source, target, since, function(err, conflicts, cps) { if (err) return next(err); + if (conflicts.length) { return next(new Error('Unexpected conflicts\n' + conflicts.map(JSON.stringify).join('\n'))); } + next(); }); }; @@ -1482,6 +1582,7 @@ describe('Replication / Change APIs', function() { var self = this; fn(function(err) { if (err) return cb(err); + bulkUpdate.call(self, data, cb); }); @@ -1494,11 +1595,14 @@ describe('Replication / Change APIs', function() { return function verify(next) { source.findById(id, function(err, expected) { if (err) return next(err); + target.findById(id, function(err, actual) { if (err) return next(err); + expect(actual && actual.toObject()) .to.eql(expected && expected.toObject()); debug('replicated instance: %j', actual); + next(); }); }); diff --git a/test/rest.middleware.test.js b/test/rest.middleware.test.js index 614209049..2e372f0ea 100644 --- a/test/rest.middleware.test.js +++ b/test/rest.middleware.test.js @@ -34,6 +34,7 @@ describe('loopback.rest', function() { .del('/mymodels/' + inst.id) .expect(200, function(err, res) { expect(res.body.count).to.equal(1); + done(); }); }); @@ -45,12 +46,12 @@ describe('loopback.rest', function() { request(app).get('/mymodels/1') .expect(404) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var errorResponse = res.body.error; assert(errorResponse); assert.equal(errorResponse.code, 'MODEL_NOT_FOUND'); + done(); }); }); @@ -70,7 +71,9 @@ describe('loopback.rest', function() { .expect(200) .end(function(err, res) { if (err) return done(err); - expect(res.body).to.eql({exists: false}); + + expect(res.body).to.eql({ exists: false }); + done(); }); }); @@ -103,7 +106,9 @@ describe('loopback.rest', function() { .expect(200) .end(function(err, res) { if (err) return done(err); - expect(res.body).to.eql({exists: true}); + + expect(res.body).to.eql({ exists: true }); + done(); }); }); @@ -177,12 +182,15 @@ describe('loopback.rest', function() { app.use(loopback.rest()); givenLoggedInUser(function(err, token) { if (err) return done(err); + request(app).get('/users/getToken') .set('Authorization', token.id) .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body.id).to.equal(null); + done(); }); }, done); @@ -194,7 +202,9 @@ describe('loopback.rest', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body).to.eql([]); + done(); }); }); @@ -205,7 +215,9 @@ describe('loopback.rest', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body).to.eql({}); + done(); }); }); @@ -262,12 +274,15 @@ describe('loopback.rest', function() { function invokeGetToken(done) { givenLoggedInUser(function(err, token) { if (err) return done(err); + request(app).get('/users/getToken') .set('Authorization', token.id) .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body.id).to.equal(token.id); + done(); }); }); @@ -296,6 +311,7 @@ describe('loopback.rest', function() { { model: app.registry.getModelByType('AccessToken') })); app.use(function(req, res, next) { loopback.getCurrentContext().set('accessToken', req.accessToken); + next(); }); app.use(loopback.rest()); @@ -351,6 +367,7 @@ describe('loopback.rest', function() { User.create(credentials, function(err, user) { if (err) return done(err); + User.login(credentials, cb); }); } @@ -399,7 +416,9 @@ describe('loopback.rest', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body.count).to.equal(3); + done(); }); }); @@ -445,7 +464,9 @@ describe('loopback.rest', function() { .expect(200) .end(function(err, res) { if (err) return done(err); + expect(res.body.count).to.equal(3); + done(); }); }); diff --git a/test/role.test.js b/test/role.test.js index e2de4f928..0c98fbcea 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -464,7 +464,9 @@ describe('role model', function() { Role.isOwner(User, user.id, user.id, function(err, result) { if (err) return done(err); + expect(result, 'isOwner result').to.equal(true); + done(); }); }); diff --git a/test/user.integration.js b/test/user.integration.js index b3a82cd56..753df249b 100644 --- a/test/user.integration.js +++ b/test/user.integration.js @@ -30,10 +30,13 @@ describe('users - integration', function() { app.models.AccessToken.belongsTo(app.models.User); app.models.User.destroyAll(function(err) { if (err) return done(err); + app.models.post.destroyAll(function(err) { if (err) return done(err); + app.models.blog.destroyAll(function(err) { if (err) return done(err); + done(); }); }); @@ -49,8 +52,10 @@ describe('users - integration', function() { .send({username: 'x', email: 'x@y.com', password: 'x'}) .expect(200, function(err, res) { if (err) return done(err); + expect(res.body.id).to.exist; userId = res.body.id; + done(); }); }); @@ -61,11 +66,11 @@ describe('users - integration', function() { this.post(url) .send({username: 'x', email: 'x@y.com', password: 'x'}) .expect(200, function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + expect(res.body.id).to.exist; accessToken = res.body.id; + done(); }); }); @@ -75,12 +80,12 @@ describe('users - integration', function() { this.post(url) .send({title: 'T1', content: 'C1'}) .expect(200, function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + expect(res.body.title).to.be.eql('T1'); expect(res.body.content).to.be.eql('C1'); expect(res.body.userId).to.be.eql(userId); + done(); }); }); @@ -91,13 +96,13 @@ describe('users - integration', function() { var url = '/api/posts?filter={"include":{"user":"accessTokens"}}'; this.get(url) .expect(200, function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + expect(res.body).to.have.property('length', 1); var post = res.body[0]; expect(post.user).to.have.property('username', 'x'); expect(post.user).to.not.have.property('accessTokens'); + done(); }); }); @@ -113,11 +118,11 @@ describe('users - integration', function() { this.post(url) .send({username: 'x', email: 'x@y.com', password: 'x'}) .expect(200, function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + expect(res.body.id).to.exist; userId = res.body.id; + done(); }); }); @@ -128,11 +133,11 @@ describe('users - integration', function() { this.post(url) .send({username: 'x', email: 'x@y.com', password: 'x'}) .expect(200, function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + expect(res.body.id).to.exist; accessToken = res.body.id; + done(); }); }); @@ -146,9 +151,11 @@ describe('users - integration', function() { console.error(err); return done(err); } + expect(res.body.title).to.be.eql('T1'); expect(res.body.content).to.be.eql('C1'); expect(res.body.userId).to.be.eql(userId); + done(); }); }); @@ -157,13 +164,13 @@ describe('users - integration', function() { var url = '/api/blogs?filter={"include":{"user":"accessTokens"}}'; this.get(url) .expect(200, function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + expect(res.body).to.have.property('length', 1); var blog = res.body[0]; expect(blog.user).to.have.property('username', 'x'); expect(blog.user).to.not.have.property('accessTokens'); + done(); }); }); diff --git a/test/user.test.js b/test/user.test.js index e4839c6e2..28e9650cc 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -67,6 +67,7 @@ describe('User', function() { User.create(validCredentials, function(err, user) { if (err) return done(err); + User.create(validCredentialsEmailVerified, done); }); }); @@ -77,6 +78,7 @@ describe('User', function() { assert(!err); assert(user.id); assert(user.email); + done(); }); }); @@ -85,8 +87,10 @@ describe('User', function() { User.settings.caseSensitiveEmail = false; User.create({email: 'F@b.com', password: 'bar'}, function(err, user) { if (err) return done(err); + assert(user.id); assert.equal(user.email, user.email.toLowerCase()); + done(); }); }); @@ -94,9 +98,11 @@ describe('User', function() { it('Create a new user (email case-sensitive)', function(done) { User.create({email: 'F@b.com', password: 'bar'}, function(err, user) { if (err) return done(err); + assert(user.id); assert(user.email); assert.notEqual(user.email, user.email.toLowerCase()); + done(); }); }); @@ -110,8 +116,9 @@ describe('User', function() { User.findById(user.id, function(err, user) { assert(user.id); assert(user.email); - assert.deepEqual(user.credentials, {cert: 'xxxxx', key: '111'}); - assert.deepEqual(user.challenges, {x: 'X', a: 1}); + assert.deepEqual(user.credentials, { cert: 'xxxxx', key: '111' }); + assert.deepEqual(user.challenges, { x: 'X', a: 1 }); + done(); }); }); @@ -138,6 +145,7 @@ describe('User', function() { User.create({email: 'c@d.com'}, function(err) { assert(err); + done(); }); }); @@ -145,6 +153,7 @@ describe('User', function() { it('Requires a valid email', function(done) { User.create({email: 'foo@', password: '123'}, function(err) { assert(err); + done(); }); }); @@ -153,6 +162,7 @@ describe('User', function() { User.create({email: 'a@b.com', password: 'foobar'}, function() { User.create({email: 'a@b.com', password: 'batbaz'}, function(err) { assert(err, 'should error because the email is not unique!'); + done(); }); }); @@ -162,8 +172,10 @@ describe('User', function() { User.settings.caseSensitiveEmail = false; User.create({email: 'A@b.com', password: 'foobar'}, function(err) { if (err) return done(err); - User.create({email: 'a@b.com', password: 'batbaz'}, function(err) { + + User.create({ email: 'a@b.com', password: 'batbaz' }, function(err) { assert(err, 'should error because the email is not unique!'); + done(); }); }); @@ -173,7 +185,9 @@ describe('User', function() { User.create({email: 'A@b.com', password: 'foobar'}, function(err, user1) { User.create({email: 'a@b.com', password: 'batbaz'}, function(err, user2) { if (err) return done(err); + assert.notEqual(user1.email, user2.email); + done(); }); }); @@ -183,6 +197,7 @@ describe('User', function() { User.create({email: 'a@b.com', username: 'abc', password: 'foobar'}, function() { User.create({email: 'b@b.com', username: 'abc', password: 'batbaz'}, function(err) { assert(err, 'should error because the username is not unique!'); + done(); }); }); @@ -194,6 +209,7 @@ describe('User', function() { assert(!accessToken, 'should not create a accessToken without a valid password'); assert(err, 'should not login without a password'); assert.equal(err.code, 'LOGIN_FAILED'); + done(); }); }); @@ -258,10 +274,10 @@ describe('User', function() { .expect(200) .send(validCredentialsEmailVerifiedOverREST) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + assert(!res.body.emailVerified); + done(); }); }); @@ -271,6 +287,7 @@ describe('User', function() { it('Should not throw an error if the query does not contain {where: }', function(done) { User.find({}, function(err) { if (err) done(err); + done(); }); }); @@ -279,8 +296,10 @@ describe('User', function() { User.settings.caseSensitiveEmail = false; User.find({where:{email: validMixedCaseEmailCredentials.email}}, function(err, result) { if (err) done(err); + assert(result[0], 'The query did not find the user'); assert.equal(result[0].email, validCredentialsEmail); + done(); }); }); @@ -303,6 +322,7 @@ describe('User', function() { assert(accessToken.userId); assert(accessToken.id); assert.equal(accessToken.id.length, 64); + done(); }); }); @@ -310,6 +330,7 @@ describe('User', function() { it('Try to login with invalid email case', function(done) { User.login(validMixedCaseEmailCredentials, function(err, accessToken) { assert(err); + done(); }); }); @@ -336,6 +357,7 @@ describe('User', function() { assert(accessToken.id); assert.equal(accessToken.ttl, 120); assert.equal(accessToken.id.length, 64); + done(); }); }); @@ -354,6 +376,7 @@ describe('User', function() { assert(accessToken.id); assert.equal(accessToken.ttl, 120); assert.equal(accessToken.id.length, 64); + done(); }) .catch(function(err) { @@ -384,6 +407,7 @@ describe('User', function() { assert.equal(accessToken.id.length, 64); // Restore create access token User.prototype.createAccessToken = createToken; + done(); }); }); @@ -414,6 +438,7 @@ describe('User', function() { assert.equal(accessToken.scopes, 'default'); // Restore create access token User.prototype.createAccessToken = createToken; + done(); }); }); @@ -425,6 +450,7 @@ describe('User', function() { assert(err); assert.equal(err.code, 'LOGIN_FAILED'); assert(!accessToken); + done(); }); }); @@ -433,11 +459,13 @@ describe('User', function() { User.login(invalidCredentials) .then(function(accessToken) { assert(!accessToken); + done(); }) .catch(function(err) { assert(err); assert.equal(err.code, 'LOGIN_FAILED'); + done(); }); }); @@ -446,6 +474,7 @@ describe('User', function() { User.login(incompleteCredentials, function(err, accessToken) { assert(err); assert.equal(err.code, 'USERNAME_EMAIL_REQUIRED'); + done(); }); }); @@ -454,11 +483,13 @@ describe('User', function() { User.login(incompleteCredentials) .then(function(accessToken) { assert(!accessToken); + done(); }) .catch(function(err) { assert(err); assert.equal(err.code, 'USERNAME_EMAIL_REQUIRED'); + done(); }); }); @@ -470,9 +501,8 @@ describe('User', function() { .expect(200) .send(validCredentials) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var accessToken = res.body; assert(accessToken.userId); @@ -491,11 +521,11 @@ describe('User', function() { .expect(401) .send(invalidCredentials) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var errorResponse = res.body.error; assert.equal(errorResponse.code, 'LOGIN_FAILED'); + done(); }); }); @@ -507,11 +537,11 @@ describe('User', function() { .expect(400) .send(incompleteCredentials) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var errorResponse = res.body.error; assert.equal(errorResponse.code, 'USERNAME_EMAIL_REQUIRED'); + done(); }); }); @@ -524,11 +554,11 @@ describe('User', function() { .expect(400) .send(JSON.stringify(validCredentials)) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var errorResponse = res.body.error; assert.equal(errorResponse.code, 'USERNAME_EMAIL_REQUIRED'); + done(); }); }); @@ -540,13 +570,13 @@ describe('User', function() { .expect(200) .expect('Content-Type', /json/) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var token = res.body; expect(token.user, 'body.user').to.not.equal(undefined); expect(token.user, 'body.user') .to.have.property('email', validCredentials.email); + done(); }); }); @@ -558,13 +588,13 @@ describe('User', function() { .expect(200) .expect('Content-Type', /json/) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var token = res.body; expect(token.user, 'body.user').to.not.equal(undefined); expect(token.user, 'body.user') .to.have.property('email', validCredentials.email); + done(); }); }); @@ -591,6 +621,7 @@ describe('User', function() { // error message should be "login failed" and not "login failed as the email has not been verified" assert(err && !/verified/.test(err.message), ('expecting "login failed" error message, received: "' + err.message + '"')); assert.equal(err.code, 'LOGIN_FAILED'); + done(); }); }); @@ -605,6 +636,7 @@ describe('User', function() { // error message should be "login failed" and not "login failed as the email has not been verified" assert(err && !/verified/.test(err.message), ('expecting "login failed" error message, received: "' + err.message + '"')); assert.equal(err.code, 'LOGIN_FAILED'); + done(); }); }); @@ -613,6 +645,7 @@ describe('User', function() { User.login(validCredentials, function(err, accessToken) { assert(err); assert.equal(err.code, 'LOGIN_FAILED_EMAIL_NOT_VERIFIED'); + done(); }); }); @@ -625,6 +658,7 @@ describe('User', function() { .catch(function(err) { assert(err); assert.equal(err.code, 'LOGIN_FAILED_EMAIL_NOT_VERIFIED'); + done(); }); }); @@ -632,6 +666,7 @@ describe('User', function() { it('Login a user by with email verification', function(done) { User.login(validCredentialsEmailVerified, function(err, accessToken) { assertGoodToken(accessToken); + done(); }); }); @@ -640,6 +675,7 @@ describe('User', function() { User.login(validCredentialsEmailVerified) .then(function(accessToken) { assertGoodToken(accessToken); + done(); }) .catch(function(err) { @@ -654,9 +690,8 @@ describe('User', function() { .expect(200) .send(validCredentialsEmailVerified) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var accessToken = res.body; assertGoodToken(accessToken); @@ -673,14 +708,14 @@ describe('User', function() { .expect(401) .send({ email: validCredentialsEmail }) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + // strongloop/loopback#931 // error message should be "login failed" and not "login failed as the email has not been verified" var errorResponse = res.body.error; assert(errorResponse && !/verified/.test(errorResponse.message), ('expecting "login failed" error message, received: "' + errorResponse.message + '"')); assert.equal(errorResponse.code, 'LOGIN_FAILED'); + done(); }); }); @@ -692,11 +727,11 @@ describe('User', function() { .expect(401) .send(validCredentials) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var errorResponse = res.body.error; assert.equal(errorResponse.code, 'LOGIN_FAILED_EMAIL_NOT_VERIFIED'); + done(); }); }); @@ -782,9 +817,8 @@ describe('User', function() { var user1; beforeEach(function(done) { User.create(realm1User, function(err, u) { - if (err) { - return done(err); - } + if (err) return done(err); + user1 = u; User.create(realm2User, done); }); @@ -794,6 +828,7 @@ describe('User', function() { User.login(credentialWithoutRealm, function(err, accessToken) { assert(err); assert.equal(err.code, 'REALM_REQUIRED'); + done(); }); }); @@ -802,6 +837,7 @@ describe('User', function() { User.login(credentialWithBadRealm, function(err, accessToken) { assert(err); assert.equal(err.code, 'LOGIN_FAILED'); + done(); }); }); @@ -810,6 +846,7 @@ describe('User', function() { User.login(credentialWithBadPass, function(err, accessToken) { assert(err); assert.equal(err.code, 'LOGIN_FAILED'); + done(); }); }); @@ -818,6 +855,7 @@ describe('User', function() { User.login(credentialWithRealm, function(err, accessToken) { assertGoodToken(accessToken); assert.equal(accessToken.userId, user1.id); + done(); }); }); @@ -826,6 +864,7 @@ describe('User', function() { User.login(credentialRealmInUsername, function(err, accessToken) { assertGoodToken(accessToken); assert.equal(accessToken.userId, user1.id); + done(); }); }); @@ -834,6 +873,7 @@ describe('User', function() { User.login(credentialRealmInEmail, function(err, accessToken) { assertGoodToken(accessToken); assert.equal(accessToken.userId, user1.id); + done(); }); }); @@ -851,6 +891,7 @@ describe('User', function() { User.login(credentialWithRealm, function(err, accessToken) { assertGoodToken(accessToken); assert.equal(accessToken.userId, user1.id); + done(); }); }); @@ -860,6 +901,7 @@ describe('User', function() { User.login(credentialRealmInEmail, function(err, accessToken) { assert(err); assert.equal(err.code, 'REALM_REQUIRED'); + done(); }); }); @@ -904,9 +946,8 @@ describe('User', function() { .expect(200) .send({email: 'foo@bar.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var accessToken = res.body; assert(accessToken.userId); @@ -929,12 +970,11 @@ describe('User', function() { assert(token); return function(err) { - if (err) { - return done(err); - } + if (err) return done(err); AccessToken.findById(token, function(err, accessToken) { assert(!accessToken, 'accessToken should not exist after logging out'); + done(err); }); }; @@ -946,6 +986,7 @@ describe('User', function() { var u = new User({username: 'foo', password: 'bar'}); u.hasPassword('bar', function(err, isMatch) { assert(isMatch, 'password doesnt match'); + done(); }); }); @@ -955,6 +996,7 @@ describe('User', function() { u.hasPassword('bar') .then(function(isMatch) { assert(isMatch, 'password doesnt match'); + done(); }) .catch(function(err) { @@ -969,6 +1011,7 @@ describe('User', function() { User.findById(user.id, function(err, uu) { uu.hasPassword('b', function(err, isMatch) { assert(isMatch); + done(); }); }); @@ -988,6 +1031,7 @@ describe('User', function() { User.findById(user.id, function(err, uu) { uu.hasPassword('baz2', function(err, isMatch) { assert(isMatch); + done(); }); }); @@ -1022,6 +1066,7 @@ describe('User', function() { var msg = result.email.response.toString('utf-8'); assert(~msg.indexOf('/api/test-users/confirm')); assert(~msg.indexOf('To: bar@bat.com')); + done(); }); }); @@ -1032,9 +1077,7 @@ describe('User', function() { .expect(200) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); }); @@ -1059,6 +1102,7 @@ describe('User', function() { var msg = result.email.response.toString('utf-8'); assert(~msg.indexOf('/api/test-users/confirm')); assert(~msg.indexOf('To: bar@bat.com')); + done(); }) .catch(function(err) { @@ -1072,9 +1116,7 @@ describe('User', function() { .expect('Content-Type', /json/) .expect(200) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); }); @@ -1095,6 +1137,7 @@ describe('User', function() { user.verify(options, function(err, result) { assert(result.email); assert.equal(result.email.messageId, 'custom-header-value'); + done(); }); }); @@ -1105,9 +1148,7 @@ describe('User', function() { .expect(200) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); }); @@ -1141,6 +1182,7 @@ describe('User', function() { assert.equal(result.token, 'token-123456'); var msg = result.email.response.toString('utf-8'); assert(~msg.indexOf('token-123456')); + done(); }); }); @@ -1151,9 +1193,7 @@ describe('User', function() { .expect(200) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); }); @@ -1180,6 +1220,7 @@ describe('User', function() { assert(err); assert.equal(err.message, 'Fake error'); assert.equal(result, undefined); + done(); }); }); @@ -1190,9 +1231,7 @@ describe('User', function() { .expect(200) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); }); @@ -1214,6 +1253,7 @@ describe('User', function() { user.verify(options, function(err, result) { var msg = result.email.response.toString('utf-8'); assert(~msg.indexOf('http://myapp.org:3000/')); + done(); }); }); @@ -1224,9 +1264,7 @@ describe('User', function() { .expect(200) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); }); @@ -1247,6 +1285,7 @@ describe('User', function() { user.verify(options, function(err, result) { var msg = result.email.response.toString('utf-8'); assert(~msg.indexOf('http://myapp.org/')); + done(); }); }); @@ -1257,9 +1296,7 @@ describe('User', function() { .expect(200) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); }); @@ -1280,6 +1317,7 @@ describe('User', function() { user.verify(options, function(err, result) { var msg = result.email.response.toString('utf-8'); assert(~msg.indexOf('https://myapp.org:3000/')); + done(); }); }); @@ -1290,9 +1328,7 @@ describe('User', function() { .expect(200) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); }); @@ -1313,6 +1349,7 @@ describe('User', function() { user.verify(options, function(err, result) { var msg = result.email.response.toString('utf-8'); assert(~msg.indexOf('https://myapp.org/')); + done(); }); }); @@ -1323,9 +1360,7 @@ describe('User', function() { .expect(200) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); }); }); @@ -1334,6 +1369,7 @@ describe('User', function() { var user = new User({email: 'bar@bat.com', password: 'bar', verificationToken: 'a-token' }); var data = user.toJSON(); assert(!('verificationToken' in data)); + done(); }); }); @@ -1355,9 +1391,8 @@ describe('User', function() { }; user.verify(options, function(err, result) { - if (err) { - return done(err); - } + if (err) return done(err); + testFunc(result, done); }); }); @@ -1368,9 +1403,7 @@ describe('User', function() { .expect(302) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); }); } @@ -1382,9 +1415,8 @@ describe('User', function() { '&redirect=' + encodeURIComponent(options.redirect)) .expect(302) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + done(); }); }, done); @@ -1420,12 +1452,12 @@ describe('User', function() { '&redirect=' + encodeURIComponent(options.redirect)) .expect(404) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var errorResponse = res.body.error; assert(errorResponse); assert.equal(errorResponse.code, 'USER_NOT_FOUND'); + done(); }); }, done); @@ -1439,12 +1471,12 @@ describe('User', function() { '&redirect=' + encodeURIComponent(options.redirect)) .expect(400) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var errorResponse = res.body.error; assert(errorResponse); assert.equal(errorResponse.code, 'INVALID_TOKEN'); + done(); }); }, done); @@ -1460,6 +1492,7 @@ describe('User', function() { User.resetPassword({ }, function(err) { assert(err); assert.equal(err.code, 'EMAIL_REQUIRED'); + done(); }); }); @@ -1472,6 +1505,7 @@ describe('User', function() { .catch(function(err) { assert(err); assert.equal(err.code, 'EMAIL_REQUIRED'); + done(); }); }); @@ -1481,6 +1515,7 @@ describe('User', function() { assert(err); assert.equal(err.code, 'EMAIL_NOT_FOUND'); assert.equal(err.statusCode, 404); + done(); }); }); @@ -1502,7 +1537,9 @@ describe('User', function() { assert(calledBack); info.accessToken.user(function(err, user) { if (err) return done(err); + assert.equal(user.email, email); + done(); }); }); @@ -1515,12 +1552,12 @@ describe('User', function() { .expect(400) .send({ }) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + var errorResponse = res.body.error; assert(errorResponse); assert.equal(errorResponse.code, 'EMAIL_REQUIRED'); + done(); }); }); @@ -1532,10 +1569,10 @@ describe('User', function() { .expect(204) .send({ email: email }) .end(function(err, res) { - if (err) { - return done(err); - } + if (err) return done(err); + assert.deepEqual(res.body, { }); + done(); }); }); diff --git a/test/util/model-tests.js b/test/util/model-tests.js index d5509ca38..d554c2d48 100644 --- a/test/util/model-tests.js +++ b/test/util/model-tests.js @@ -130,6 +130,7 @@ module.exports = function defineModelTestsWithDataSource(options) { user.isValid(function(valid) { assert(valid === false); assert(user.errors.age, 'model should have age error'); + done(); }); }); @@ -139,6 +140,7 @@ module.exports = function defineModelTestsWithDataSource(options) { it('Create an instance of Model with given data and save to the attached data source', function(done) { User.create({first: 'Joe', last: 'Bob'}, function(err, user) { assert(user instanceof User); + done(); }); }); @@ -151,6 +153,7 @@ module.exports = function defineModelTestsWithDataSource(options) { assert(user.id); assert(!err); assert(!user.errors); + done(); }); }); @@ -170,6 +173,7 @@ module.exports = function defineModelTestsWithDataSource(options) { assert.equal(updatedUser.first, 'updatedFirst'); assert.equal(updatedUser.last, 'updatedLast'); assert.equal(updatedUser.age, 100); + done(); }); }); @@ -185,6 +189,7 @@ module.exports = function defineModelTestsWithDataSource(options) { User.upsert({first: 'bob', id: 7}, function(err, updatedUser) { assert(!err); assert.equal(updatedUser.first, 'bob'); + done(); }); }); @@ -196,12 +201,16 @@ module.exports = function defineModelTestsWithDataSource(options) { User.create({first: 'joe', last: 'bob'}, function(err, user) { User.findById(user.id, function(err, foundUser) { if (err) return done(err); + assert.equal(user.id, foundUser.id); User.deleteById(foundUser.id, function(err) { if (err) return done(err); - User.find({ where: { id: user.id } }, function(err, found) { + + User.find({ where: { id: user.id }}, function(err, found) { if (err) return done(err); + assert.equal(found.length, 0); + done(); }); }); @@ -216,6 +225,7 @@ module.exports = function defineModelTestsWithDataSource(options) { User.deleteById(user.id, function(err) { User.findById(user.id, function(err, notFound) { assert.equal(notFound, null); + done(); }); }); @@ -230,6 +240,7 @@ module.exports = function defineModelTestsWithDataSource(options) { assert.equal(user.id, 23); assert.equal(user.first, 'michael'); assert.equal(user.last, 'jordan'); + done(); }); }); @@ -247,6 +258,7 @@ module.exports = function defineModelTestsWithDataSource(options) { .on('done', function() { User.count({age: {gt: 99}}, function(err, count) { assert.equal(count, 2); + done(); }); }); From 78688037114ac4e61fb1011791d4d0547c90a9c5 Mon Sep 17 00:00:00 2001 From: Rik Date: Sun, 8 May 2016 13:10:56 +0200 Subject: [PATCH 33/35] Update user.js allow to change all {href} instances in user.verify() mail into generated url instead of just one --- common/models/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/user.js b/common/models/user.js index 7466ca8e7..4263e354e 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -420,7 +420,7 @@ module.exports = function(User) { options.text = options.text || 'Please verify your email by opening this link in a web browser:\n\t{href}'; - options.text = options.text.replace('{href}', options.verifyHref); + options.text = options.text.replace(/\{href\}/g, options.verifyHref); options.to = options.to || user.email; From 8fef4845f8f40deb5b2349a2a36ed3f1c50be555 Mon Sep 17 00:00:00 2001 From: juehou Date: Tue, 26 Apr 2016 00:35:54 -0400 Subject: [PATCH 34/35] Resolver support return promise --- common/models/role.js | 14 +++++++++++--- test/role.test.js | 20 ++++++++++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/common/models/role.js b/common/models/role.js index b8c1242d5..c4e7e8b3e 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -128,8 +128,9 @@ module.exports = function(Role) { /** * Add custom handler for roles. * @param {String} role Name of role. - * @param {Function} resolver Function that determines if a principal is in the specified role. - * Signature must be `function(role, context, callback)` + * @param {Function} resolver Function that determines + * if a principal is in the specified role. + * Should provide a callback or return a promise. */ Role.registerResolver = function(role, resolver) { if (!Role.resolvers) { @@ -295,7 +296,14 @@ module.exports = function(Role) { var resolver = Role.resolvers[role]; if (resolver) { debug('Custom resolver found for role %s', role); - resolver(role, context, callback); + + var promise = resolver(role, context, callback); + if (promise && typeof promise.then === 'function') { + promise.then( + function(result) { callback(null, result); }, + callback + ); + } return; } diff --git a/test/role.test.js b/test/role.test.js index 0c98fbcea..50a5e0321 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -13,6 +13,7 @@ var Application = loopback.Application; var ACL = loopback.ACL; var async = require('async'); var expect = require('chai').expect; +var Promise = require('bluebird'); function checkResult(err, result) { assert(!err); @@ -181,6 +182,13 @@ describe('role model', function() { }); it('should support owner role resolver', function() { + Role.registerResolver('returnPromise', function(role, context) { + return new Promise(function(resolve) { + process.nextTick(function() { + resolve(true); + }); + }); + }); var Album = ds.createModel('Album', { name: String, @@ -196,10 +204,18 @@ describe('role model', function() { }); User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) { - Role.isInRole(Role.AUTHENTICATED, {principalType: ACL.USER, principalId: user.id}, function(err, yes) { + Role.isInRole('returnPromise', { principalType: ACL.USER, principalId: user.id }, + function(err, yes) { assert(!err && yes); }); - Role.isInRole(Role.AUTHENTICATED, {principalType: ACL.USER, principalId: null}, function(err, yes) { + + Role.isInRole(Role.AUTHENTICATED, { principalType: ACL.USER, principalId: user.id }, + function(err, yes) { + assert(!err && yes); + }); + + Role.isInRole(Role.AUTHENTICATED, { principalType: ACL.USER, principalId: null }, + function(err, yes) { assert(!err && !yes); }); From 8c2858d4e0c1dee597d4edc4d701198e37b95aa3 Mon Sep 17 00:00:00 2001 From: Amir Jafarian Date: Tue, 10 May 2016 21:33:23 -0400 Subject: [PATCH 35/35] Initial commit --- common/models/user.json | 6 ++++ lib/persisted-model.js | 67 ++++++++++++++++++++++++++++++++++-- test/model.test.js | 2 ++ test/remoting.integration.js | 5 ++- 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/common/models/user.json b/common/models/user.json index 16545ab4b..8ff1847b9 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -87,6 +87,12 @@ "permission": "ALLOW", "property": "resetPassword", "accessType": "EXECUTE" + }, + { + "principalType": "ROLE", + "principalId": "$owner", + "permission": "ALLOW", + "property": "replaceById" } ], "relations": { diff --git a/lib/persisted-model.js b/lib/persisted-model.js index 734992795..e7eb46051 100644 --- a/lib/persisted-model.js +++ b/lib/persisted-model.js @@ -115,6 +115,18 @@ module.exports = function(registry) { throwNotAttached(this.modelName, 'upsert'); }; + /** + * Replace or insert a model instance + * @param {Object} data The model instance data to insert. + * @callback {Function} callback Callback function called with `cb(err, obj)` signature. + * @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object). + * @param {Object} model Replaced model instance. + */ + + PersistedModel.replaceOrCreate = function replaceOrCreate(data, cb) { + throwNotAttached(this.modelName, 'replaceOrCreate'); + }; + /** * Finds one record matching the optional filter object. If not found, creates * the object using the data provided as second argument. In this sense it is @@ -482,6 +494,19 @@ module.exports = function(registry) { throwNotAttached(this.modelName, 'updateAttributes'); }; + /** + * Replace set of attributes. Performs validation before replacing. + * + * @param {Object} data Data to replace. + * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object). + * @param {Object} instance Repalced instance. + */ + + PersistedModel.replaceById = function replaceById(data, cb) { + throwNotAttached(this.modelName, 'replaceById'); + }; + /** * Reload object from persistence. Requires `id` member of `object` to be able to call `find`. * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required. @@ -549,6 +574,21 @@ module.exports = function(registry) { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; + // This is the defualt verb used for 2.X + var configurableVerb = { + updateAttributes: 'put', + updateOrCreate: 'put', + replaceOrCreate: 'patch', + replaceById: 'patch', + }; + + // we check options.newMapping when backporting to `2.x`. Thought? + if (options.newMapping) { + configurableVerb.replaceById = 'put'; + configurableVerb.replaceOrCreate = 'put'; + configurableVerb.updateAttributes = 'patch'; + configurableVerb.updateOrCreate = 'patch'; + } function setRemoting(scope, name, options) { var fn = scope[name]; @@ -571,7 +611,16 @@ module.exports = function(registry) { accessType: 'WRITE', accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, returns: {arg: 'data', type: typeName, root: true}, - http: {verb: 'put', path: '/'} + http: { verb: configurableVerb.updateOrCreate, path: '/' }, + }); + + setRemoting(PersistedModel, 'replaceOrCreate', { + description: 'Replace an existing model instance or insert a new one into the data source.', + accessType: 'WRITE', + accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description: + 'Model instance data' }, + returns: { arg: 'data', type: typeName, root: true }, + http: { verb: configurableVerb.replaceOrCreate, path: '/' }, }); setRemoting(PersistedModel, 'exists', { @@ -619,6 +668,20 @@ module.exports = function(registry) { rest: {after: convertNullToNotFoundError} }); + setRemoting(PersistedModel, 'replaceById', { + description: 'Replace attributes for a model instance and persist it into the data source.', + accessType: 'WRITE', + accepts: [ + { arg: 'id', type: 'any', description: 'Model id', required: true, + http: { source: 'put' }}, + { arg: 'data', type: 'object', http: { source: 'body' }, description: + 'An object of model property name/value pairs' }, + ], + returns: { arg: 'data', type: typeName, root: true }, + http: { verb: configurableVerb.replaceById, path: '/:id' }, + rest: { after: convertNullToNotFoundError }, + }); + setRemoting(PersistedModel, 'find', { description: 'Find all instances of the model matched by filter from the data source.', accessType: 'READ', @@ -692,7 +755,7 @@ module.exports = function(registry) { accessType: 'WRITE', accepts: {arg: 'data', type: 'object', http: {source: 'body'}, description: 'An object of model property name/value pairs'}, returns: {arg: 'data', type: typeName, root: true}, - http: {verb: 'put', path: '/'} + http: {verb: configurableVerb.updateAttributes, path: '/'} }); if (options.trackChanges || options.enableRemoteReplication) { diff --git a/test/model.test.js b/test/model.test.js index a02690011..2991f98e3 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -645,6 +645,8 @@ describe.onServer('Remote Methods', function() { 'upsert', 'updateOrCreate', 'exists', 'findById', + 'replaceById', + 'replaceOrCreate', 'find', 'findOne', 'updateAll', 'update', diff --git a/test/remoting.integration.js b/test/remoting.integration.js index 5154bb473..bf2bb9f96 100644 --- a/test/remoting.integration.js +++ b/test/remoting.integration.js @@ -122,12 +122,15 @@ describe('remoting - integration', function() { .map(function(m) { return formatMethod(m); }); - + // This is the list of expected default endpoints + // for LB 3.X (They are different for LB 2.X) var expectedMethods = [ 'create(data:object):store POST /stores', 'upsert(data:object):store PUT /stores', + 'replaceOrCreate(data:object):store PATCH /stores', 'exists(id:any):boolean GET /stores/:id/exists', 'findById(id:any,filter:object):store GET /stores/:id', + 'replaceById(id:any,data:object):store PATCH /stores/:id', 'find(filter:object):store GET /stores', 'findOne(filter:object):store GET /stores/findOne', 'updateAll(where:object,data:object):object POST /stores/update',