diff --git a/.travis.yml b/.travis.yml
index 168369451..d431fa429 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,5 +3,6 @@ language: node_js
node_js:
- "0.10"
- "0.12"
- - "iojs"
+ - "4"
+ - "6"
diff --git a/CHANGES.md b/CHANGES.md
index ea87f5de4..aa42725cf 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,47 @@
+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
+==========================
+
+ * 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 +1033,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 +1043,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 +1061,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 +1187,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/Gruntfile.js b/Gruntfile.js
index a7a755c81..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) {
@@ -217,7 +222,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']);
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
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 be353a039..605caa23d 100644
--- a/common/models/change.js
+++ b/common/models/change.js
@@ -1,9 +1,15 @@
+// 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.
*/
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 +83,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 +119,7 @@ module.exports = function(Change) {
}
callback();
});
+ return callback.promise;
};
/**
@@ -138,6 +147,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 +165,7 @@ module.exports = function(Change) {
Change.updateOrCreate(ch, callback);
}
});
+ return callback.promise;
};
/**
@@ -171,9 +182,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 +203,7 @@ module.exports = function(Change) {
}
);
});
+ return cb.promise;
function doRectify(checkpoint, rev) {
if (rev) {
@@ -248,6 +258,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 +269,7 @@ module.exports = function(Change) {
cb(null, null);
}
});
+ return cb.promise;
};
/**
@@ -390,8 +402,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 +470,7 @@ module.exports = function(Change) {
conflicts: conflicts
});
});
+ return callback.promise;
};
/**
diff --git a/common/models/checkpoint.js b/common/models/checkpoint.js
index 304f83148..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.
*/
@@ -27,43 +32,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/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 a176fec17..c4e7e8b3e 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');
@@ -123,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) {
@@ -147,12 +153,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;
}
/*!
@@ -292,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;
}
@@ -442,4 +453,6 @@ module.exports = function(Role) {
if (callback) callback(err, roles);
});
};
+
+ Role.validatesUniquenessOf('name', { message: 'already exists' });
};
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..4263e354e 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.
*/
@@ -415,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;
diff --git a/common/models/user.json b/common/models/user.json
index d70a89d3f..8ff1847b9 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",
@@ -87,6 +87,12 @@
"permission": "ALLOW",
"property": "resetPassword",
"accessType": "EXECUTE"
+ },
+ {
+ "principalType": "ROLE",
+ "principalId": "$owner",
+ "permission": "ALLOW",
+ "property": "replaceById"
}
],
"relations": {
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 bd3375539..1f7b5c64a 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.
*/
@@ -153,6 +158,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);
@@ -219,11 +229,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;
+ }
};
/**
@@ -397,7 +415,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;
@@ -410,6 +428,8 @@ function dataSourcesFromConfig(name, config, connectorRegistry, registry) {
config.connector = require(connectorPath);
}
}
+ if (!config.connector.name)
+ config.connector.name = name;
}
return registry.createDataSource(config);
@@ -551,7 +571,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);
}
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 f3ec47b25..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.
*/
@@ -431,6 +436,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) {
@@ -806,8 +812,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;
diff --git a/lib/persisted-model.js b/lib/persisted-model.js
index b3bb62281..e7eb46051 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.
*/
@@ -111,17 +116,46 @@ 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.
+ * 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
+ * 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.
+ * @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) {
@@ -460,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.
@@ -527,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];
@@ -549,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', {
@@ -597,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',
@@ -670,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) {
@@ -882,12 +967,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/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/package.json b/package.json
index bccf39157..2eb05c8b3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "loopback",
- "version": "2.26.2",
+ "version": "2.28.0",
"description": "LoopBack: Open Source Framework for Node.js",
"homepage": "http://loopback.io",
"keywords": [
@@ -78,13 +78,15 @@
"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",
"supertest": "^0.15.0"
},
@@ -102,8 +104,10 @@
"depd": "loopback-datasource-juggler/lib/browser.depd.js",
"bcrypt": false
},
- "license": "MIT",
- "optionalDependencies": {
- "sl-blip": "http://blip.strongloop.com/loopback@2.26.2"
- }
+ "config": {
+ "ci": {
+ "debug": "*,-mocha:*,-eslint:*"
+ }
+ },
+ "license": "MIT"
}
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 e80eb560b..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.
*/
@@ -62,6 +67,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 +87,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 +107,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/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 3125a6535..b32152573 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('../');
@@ -11,6 +16,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);
@@ -126,6 +137,7 @@ describe('access control - integration', function() {
var userCounter;
function newUserData() {
userCounter = userCounter ? ++userCounter : 1;
+
return {
email: 'new-' + userCounter + '@test.test',
password: 'test'
@@ -203,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 9e57cba2a..573cae5a4 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');
@@ -65,7 +70,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');
@@ -144,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();
});
});
@@ -159,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();
});
});
@@ -174,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();
});
});
@@ -183,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 }));
@@ -195,10 +204,105 @@ describe('loopback.token(options)', function() {
.expect(200)
.end(function(err, res) {
if (err) return done(err);
+
expect(res.body).to.eql(tokenStub);
+
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() {
@@ -217,6 +321,7 @@ describe('AccessToken', function() {
it('should be validateable', function(done) {
this.token.validate(function(err, isValid) {
assert(isValid);
+
done();
});
});
@@ -232,7 +337,9 @@ describe('AccessToken', function() {
Token.findForRequest(req, function(err, token) {
if (err) return done(err);
+
expect(token.id).to.eql(expectedTokenId);
+
done();
});
});
@@ -266,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();
});
});
@@ -282,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();
});
});
@@ -298,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();
});
});
@@ -314,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();
});
});
@@ -347,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();
});
});
@@ -357,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 d8706eec3..3022ebe6b 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;
@@ -382,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();
});
@@ -391,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 2701ef9d2..fd8d03735 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');
@@ -34,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();
});
});
@@ -48,7 +55,9 @@ describe('app', function() {
executeMiddlewareHandlers(app, function(err) {
if (err) return done(err);
+
expect(steps).to.eql(['first', 'second']);
+
done();
});
});
@@ -60,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();
});
});
@@ -80,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();
});
});
@@ -98,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();
});
});
@@ -116,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();
});
});
@@ -126,6 +143,7 @@ describe('app', function() {
app.middleware('initial', function(req, res, next) {
steps.push('initial');
+
next(expectedError);
});
@@ -133,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();
});
});
@@ -152,6 +173,7 @@ describe('app', function() {
executeMiddlewareHandlers(app, function(err) {
expect(err).to.equal(expectedError);
+
done();
});
});
@@ -170,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();
});
});
@@ -188,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();
});
});
@@ -201,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();
});
});
@@ -214,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();
});
});
@@ -222,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();
});
});
@@ -239,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',
@@ -273,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();
});
});
@@ -290,7 +329,9 @@ describe('app', function() {
executeMiddlewareHandlers(app, '/test', function(err) {
if (err) return done(err);
+
expect(steps).to.eql(['route', 'files']);
+
done();
});
});
@@ -310,7 +351,9 @@ describe('app', function() {
executeMiddlewareHandlers(app, function(err) {
if (err) return done;
+
expect(steps).to.eql(numbers);
+
done();
});
});
@@ -324,6 +367,7 @@ describe('app', function() {
mountpath: req.app.mountpath,
parent: req.app.parent
};
+
next();
});
subapp.on('mount', function() { mountWasEmitted = true; });
@@ -332,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();
});
});
@@ -350,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();
});
});
@@ -383,6 +434,7 @@ describe('app', function() {
function pathSavingHandler() {
return function(req, res, next) {
steps.push(req.originalUrl);
+
next();
};
}
@@ -406,6 +458,7 @@ describe('app', function() {
var args = Array.prototype.slice.apply(arguments);
return function(req, res, next) {
steps.push(args);
+
next();
};
};
@@ -456,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();
});
});
@@ -472,6 +527,7 @@ describe('app', function() {
function factory() {
return function(req, res, next) {
steps.push(req.originalUrl);
+
next();
};
},
@@ -485,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();
});
});
@@ -542,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();
});
}
@@ -606,6 +667,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) {
@@ -732,6 +809,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() {
@@ -755,6 +842,7 @@ describe('app', function() {
app.listen(function() {
expect(app.get('port'), 'port').to.not.equal(0);
+
done();
});
});
@@ -768,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();
});
});
@@ -778,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();
});
});
@@ -788,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();
});
}
@@ -802,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 ab7405214..cbd24ca23 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() {
@@ -17,6 +22,7 @@ describe('PersistedModel.createChangeStream()', function() {
changes.on('data', function(change) {
expect(change.type).to.equal('create');
changes.destroy();
+
done();
});
@@ -31,6 +37,7 @@ describe('PersistedModel.createChangeStream()', function() {
changes.on('data', function(change) {
expect(change.type).to.equal('update');
changes.destroy();
+
done();
});
newScore.updateAttributes({
@@ -47,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 55d551c72..220d8055f 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;
@@ -28,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();
});
});
@@ -61,6 +68,7 @@ describe('Change', function() {
var test = this;
Change.rectifyModelChanges(this.modelName, [this.modelId], function(err, trackedChanges) {
if (err) return done(err);
+
done();
});
});
@@ -69,6 +77,7 @@ describe('Change', function() {
var test = this;
Change.find(function(err, trackedChanges) {
assert.equal(trackedChanges[0].modelId, test.modelId.toString());
+
done();
});
});
@@ -76,12 +85,47 @@ describe('Change', function() {
it('should only create one change', function(done) {
Change.count(function(err, count) {
assert.equal(count, 1);
+
done();
});
});
});
});
+ 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() {
@@ -89,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();
});
});
@@ -98,7 +144,33 @@ 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();
+ });
+ });
+ });
+
+ 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();
});
});
@@ -112,6 +184,7 @@ describe('Change', function() {
modelId: test.modelId
}, function(err, change) {
test.existingChange = change;
+
done();
});
});
@@ -120,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();
});
});
@@ -128,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();
});
});
@@ -143,6 +219,7 @@ describe('Change', function() {
},
function(err, ch) {
change = ch;
+
done(err);
});
});
@@ -151,6 +228,7 @@ describe('Change', function() {
var test = this;
change.rectify(function(err, ch) {
assert.equal(ch.rev, test.revisionForModel);
+
done();
});
});
@@ -174,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);
@@ -185,7 +264,9 @@ describe('Change', function() {
function checkpoint(next) {
TestModel.checkpoint(function(err, inst) {
if (err) return next(err);
+
cp = inst.seq;
+
next();
});
}
@@ -196,6 +277,7 @@ describe('Change', function() {
model.name += 'updated';
model.save(function(err) {
test.revisionForModel = Change.revisionForInst(model);
+
next(err);
});
}
@@ -211,14 +293,40 @@ 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();
});
});
});
});
+ 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;
@@ -229,11 +337,30 @@ describe('Change', function() {
change.currentRevision(function(err, rev) {
assert.equal(rev, test.revisionForModel);
+
done();
});
});
});
+ 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() {
@@ -368,12 +495,34 @@ 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();
});
});
+ 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',
@@ -384,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();
@@ -395,6 +545,7 @@ describe('Change', function() {
prev: 'foo', // this is the current local revision
rev: 'foo-new',
});
+
done();
});
});
@@ -411,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();
@@ -422,6 +574,7 @@ describe('Change', function() {
prev: 'foo', // this is the current local revision
rev: 'foo-new',
});
+
done();
});
});
@@ -437,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();
@@ -448,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 98e248efb..b5b9093ae 100644
--- a/test/checkpoint.test.js
+++ b/test/checkpoint.test.js
@@ -1,28 +1,98 @@
+// 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;
-// 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);
+
expect(seq).to.equal(3);
+
next();
});
}
], 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);
+ });
+ });
});
});
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..e24436929 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');
@@ -19,7 +24,9 @@ describe('RemoteConnector', function() {
foo: 'bar'
}, function(err, inst) {
if (err) return done(err);
+
assert(inst.id);
+
done();
});
});
@@ -30,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 24f6967e0..ad7d57363 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');
@@ -27,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 018f543ca..be12a3a63 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');
@@ -61,6 +66,7 @@ describe('Email and SMTP', function() {
assert(mail.response);
assert(mail.envelope);
assert(mail.messageId);
+
done(err);
});
});
@@ -78,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 d19abf47e..273481240 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');
@@ -17,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();
});
});
@@ -33,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();
});
});
@@ -41,15 +48,24 @@ describe('loopback.errorHandler(options)', function() {
//arrange
var app = loopback();
app.use(loopback.urlNotFound());
- app.use(loopback.errorHandler({ includeStack: false, log: customLogger }));
+
+ var errorLogged;
+ app.use(loopback.errorHandler({
+ includeStack: false,
+ log: function customLogger(err, str, req) {
+ errorLogged = err;
+ }
+ }));
//act
- request(app).get('/url-does-not-exist').end();
+ 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');
- //assert
- function customLogger(err, str, req) {
- assert.ok(err.message === 'Cannot GET /url-does-not-exist');
done();
- }
+ });
+
});
});
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..6e3f72064 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() {
@@ -41,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();
});
});
@@ -55,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 7aae5dc0b..074bd2625 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) {
@@ -16,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/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..b3aea149f 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');
@@ -606,6 +611,7 @@ describe('loopback', function() {
}else {
ctxx.result.data = 'context not available';
}
+
next();
});
@@ -613,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();
});
});
@@ -627,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 7d9feb70e..9a223be5f 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
@@ -28,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 96d8ab756..2f6e17ca6 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;
@@ -12,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);
});
});
@@ -24,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) {
@@ -47,6 +54,7 @@ describe('Application', function() {
assert(app.created);
assert(app.modified);
assert.equal(typeof app.id, 'string');
+
done(err, result);
});
});
@@ -97,6 +105,7 @@ describe('Application', function() {
serverApiKey: 'serverKey'
}
});
+
done(err, result);
});
});
@@ -116,6 +125,7 @@ describe('Application', function() {
assert(app.created);
assert(app.modified);
registeredApp = app;
+
done(err, result);
});
});
@@ -141,6 +151,7 @@ describe('Application', function() {
assert(app.created);
assert(app.modified);
registeredApp = app;
+
done(err, result);
});
});
@@ -167,6 +178,7 @@ describe('Application', function() {
assert(app.created);
assert(app.modified);
registeredApp = app;
+
done();
})
.catch(function(err) {
@@ -180,6 +192,7 @@ describe('Application', function() {
assert(app.id);
assert(app.id === registeredApp.id);
registeredApp = app;
+
done(err, result);
});
});
@@ -191,6 +204,7 @@ describe('Application', function() {
assert(app.id);
assert(app.id === registeredApp.id);
registeredApp = app;
+
done();
})
.catch(function(err) {
@@ -203,6 +217,7 @@ describe('Application', function() {
function(err, result) {
assert.equal(result.application.id, registeredApp.id);
assert.equal(result.keyType, 'clientKey');
+
done(err, result);
});
});
@@ -211,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);
});
@@ -225,6 +241,7 @@ describe('Application', function() {
function(err, result) {
assert.equal(result.application.id, registeredApp.id);
assert.equal(result.keyType, 'javaScriptKey');
+
done(err, result);
});
});
@@ -234,6 +251,7 @@ describe('Application', function() {
function(err, result) {
assert.equal(result.application.id, registeredApp.id);
assert.equal(result.keyType, 'restApiKey');
+
done(err, result);
});
});
@@ -243,6 +261,7 @@ describe('Application', function() {
function(err, result) {
assert.equal(result.application.id, registeredApp.id);
assert.equal(result.keyType, 'masterKey');
+
done(err, result);
});
});
@@ -252,6 +271,7 @@ describe('Application', function() {
function(err, result) {
assert.equal(result.application.id, registeredApp.id);
assert.equal(result.keyType, 'windowsKey');
+
done(err, result);
});
});
@@ -260,6 +280,7 @@ describe('Application', function() {
Application.authenticate(registeredApp.id, 'invalid-key',
function(err, result) {
assert(!result);
+
done(err, result);
});
});
@@ -268,6 +289,7 @@ describe('Application', function() {
Application.authenticate(registeredApp.id, 'invalid-key')
.then(function(result) {
assert(!result);
+
done();
})
.catch(function(err) {
@@ -304,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 b9a16f36d..2991f98e3 100644
--- a/test/model.test.js
+++ b/test/model.test.js
@@ -1,9 +1,18 @@
+// 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;
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');
@@ -139,6 +148,7 @@ describe.onServer('Remote Methods', function() {
User.destroyAll(function() {
User.count(function(err, count) {
assert.equal(count, 0);
+
done();
});
});
@@ -155,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();
});
});
@@ -165,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();
});
});
@@ -183,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)
@@ -191,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();
});
});
@@ -206,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)
@@ -215,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')
@@ -222,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();
});
});
@@ -239,6 +258,7 @@ describe.onServer('Remote Methods', function() {
User.beforeRemote('create', function(ctx, user, next) {
hookCalled = true;
+
next();
});
@@ -250,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();
});
});
@@ -264,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();
});
@@ -280,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();
});
});
@@ -292,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();
});
});
@@ -318,6 +347,7 @@ describe.onServer('Remote Methods', function() {
assert(ctx.res);
assert(ctx.res.write);
assert(ctx.res.end);
+
next();
});
@@ -329,7 +359,9 @@ describe.onServer('Remote Methods', function() {
.expect(200)
.end(function(err, res) {
if (err) return done(err);
+
assert(hookCalled);
+
done();
});
});
@@ -347,6 +379,7 @@ describe.onServer('Remote Methods', function() {
assert(ctx.res);
assert(ctx.res.write);
assert(ctx.res.end);
+
next();
});
@@ -358,7 +391,9 @@ describe.onServer('Remote Methods', function() {
.expect(200)
.end(function(err, res) {
if (err) return done(err);
+
assert(hookCalled);
+
done();
});
});
@@ -383,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();
});
});
@@ -528,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();
});
});
@@ -547,6 +584,7 @@ describe.onServer('Remote Methods', function() {
if (err) return done(err);
assert.equal(result, current + 1);
+
done();
});
@@ -607,6 +645,8 @@ describe.onServer('Remote Methods', function() {
'upsert', 'updateOrCreate',
'exists',
'findById',
+ 'replaceById',
+ 'replaceOrCreate',
'find',
'findOne',
'updateAll', 'update',
@@ -618,6 +658,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() {
@@ -632,7 +686,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
@@ -641,7 +697,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 8f44ac426..2f9cda87f 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) {
@@ -38,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 70f1ae122..e853513e8 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('../');
@@ -11,6 +16,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);
@@ -68,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' });
@@ -91,12 +104,14 @@ describe('relations - integration', function() {
this.get(url)
.query({'filter': {'include' : 'pictures'}})
.expect(200, function(err, res) {
- // console.log(res.body);
+ if (err) return done(err);
+
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();
});
});
@@ -106,10 +121,12 @@ describe('relations - integration', function() {
this.get(url)
.query({'filter': {'include' : 'imageable'}})
.expect(200, function(err, res) {
- // console.log(res.body);
+ 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).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();
});
});
@@ -119,10 +136,13 @@ 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');
- 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();
});
});
@@ -133,7 +153,10 @@ 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();
});
});
@@ -184,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));
});
@@ -211,7 +236,9 @@ describe('relations - integration', function() {
storeId: this.store.id
}, function(err, count) {
if (err) return done(err);
+
assert.equal(count, 2);
+
done();
});
});
@@ -226,6 +253,7 @@ describe('relations - integration', function() {
}, function(err, widget) {
self.widget = widget;
self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id;
+
done();
});
});
@@ -299,6 +327,7 @@ describe('relations - integration', function() {
}, function(err, widget) {
self.widget = widget;
self.url = '/api/widgets/' + self.widget.id + '/store';
+
done();
});
});
@@ -333,6 +362,7 @@ describe('relations - integration', function() {
name: 'ph1'
}, function(err, physician) {
root.physician = physician;
+
done();
});
},
@@ -345,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) {
@@ -354,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) {
@@ -369,7 +401,8 @@ describe('relations - integration', function() {
self.url = root.relUrl;
self.patient = root.patient;
self.physician = root.physician;
- done();
+
+ done(err);
});
});
@@ -385,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();
});
});
@@ -394,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();
});
});
@@ -408,7 +443,8 @@ describe('relations - integration', function() {
self.url = root.relUrl;
self.patient = root.patient;
self.physician = root.physician;
- done();
+
+ done(err);
});
});
@@ -430,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();
});
});
@@ -439,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();
});
});
@@ -453,7 +491,8 @@ describe('relations - integration', function() {
self.url = root.relUrl;
self.patient = root.patient;
self.physician = root.physician;
- done();
+
+ done(err);
});
});
@@ -473,7 +512,8 @@ describe('relations - integration', function() {
'/patients/rel/' + '999';
self.patient = root.patient;
self.physician = root.physician;
- done();
+
+ done(err);
});
});
@@ -492,7 +532,8 @@ describe('relations - integration', function() {
self.url = root.relUrl;
self.patient = root.patient;
self.physician = root.physician;
- done();
+
+ done(err);
});
});
@@ -501,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();
});
});
@@ -510,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();
});
});
@@ -523,6 +566,7 @@ describe('relations - integration', function() {
var self = this;
app.models.appointment.find(function(err, apps) {
assert.equal(apps.length, 0);
+
done();
});
});
@@ -532,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();
});
});
@@ -547,7 +592,8 @@ describe('relations - integration', function() {
'/patients/' + root.patient.id;
self.patient = root.patient;
self.physician = root.physician;
- done();
+
+ done(err);
});
});
@@ -568,7 +614,8 @@ describe('relations - integration', function() {
'/patients/' + root.patient.id;
self.patient = root.patient;
self.physician = root.physician;
- done();
+
+ done(err);
});
});
@@ -581,6 +628,7 @@ describe('relations - integration', function() {
var self = this;
app.models.appointment.find(function(err, apps) {
assert.equal(apps.length, 0);
+
done();
});
});
@@ -590,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();
});
});
@@ -598,6 +647,7 @@ describe('relations - integration', function() {
var self = this;
app.models.patient.find(function(err, patients) {
assert.equal(patients.length, 0);
+
done();
});
});
@@ -629,7 +679,9 @@ describe('relations - integration', function() {
name: 'a-product'
}, function(err, product) {
if (err) return done(err);
+
test.product = product;
+
done();
});
});
@@ -638,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);
});
});
@@ -652,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();
});
});
@@ -667,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();
});
});
@@ -684,6 +741,8 @@ 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([
{
@@ -691,6 +750,7 @@ describe('relations - integration', function() {
name: expectedProduct.name
}
]);
+
done();
});
});
@@ -703,6 +763,8 @@ 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([
{
@@ -710,6 +772,7 @@ describe('relations - integration', function() {
name: expectedProduct.name
}
]);
+
done();
});
});
@@ -737,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();
});
});
@@ -755,6 +820,7 @@ describe('relations - integration', function() {
expect(res.body).to.be.eql(
{ url: 'http://image.url' }
);
+
done();
});
});
@@ -764,10 +830,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('Group 1');
expect(res.body.poster).to.be.eql(
{ url: 'http://image.url' }
);
+
done();
});
});
@@ -777,9 +846,12 @@ 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();
});
});
@@ -791,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();
});
});
@@ -800,9 +873,12 @@ 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();
});
});
@@ -841,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' });
@@ -857,11 +934,14 @@ 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();
});
});
@@ -871,10 +951,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 }
]);
+
done();
});
});
@@ -885,9 +968,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 2', id: 2 }
]);
+
done();
});
});
@@ -901,6 +987,7 @@ describe('relations - integration', function() {
.send({ content: 'Todo 3' })
.expect(200, function(err, res) {
expect(res.body).to.be.eql(expected);
+
done();
});
});
@@ -910,11 +997,14 @@ 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();
});
});
@@ -924,9 +1014,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 3', id: 3 }
);
+
done();
});
});
@@ -946,10 +1039,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 3', id: 3 }
]);
+
done();
});
});
@@ -957,9 +1053,12 @@ 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');
+
done();
});
});
@@ -1011,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' },
@@ -1025,6 +1125,7 @@ describe('relations - integration', function() {
var test = this;
app.models.ingredient.create({ name: 'Sugar' }, function(err, ing) {
test.ingredient2 = ing.id;
+
done();
});
});
@@ -1044,8 +1145,11 @@ 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();
});
});
@@ -1059,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();
});
});
@@ -1069,11 +1174,14 @@ 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();
});
});
@@ -1084,10 +1192,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: 'Butter', id: test.ingredient3 }
]);
+
done();
});
});
@@ -1099,9 +1210,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: 'Butter', id: test.ingredient3 }
]);
+
done();
});
});
@@ -1113,6 +1227,8 @@ 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
]);
@@ -1120,6 +1236,7 @@ describe('relations - integration', function() {
{ name: 'Chocolate', id: test.ingredient1 },
{ name: 'Butter', id: test.ingredient3 }
]);
+
done();
});
});
@@ -1131,9 +1248,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: 'Butter', id: test.ingredient3 }
);
+
done();
});
});
@@ -1146,8 +1266,11 @@ 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();
});
});
@@ -1169,10 +1292,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 }
]);
+
done();
});
});
@@ -1183,9 +1309,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 }
]);
+
done();
});
});
@@ -1200,6 +1329,7 @@ describe('relations - integration', function() {
expect(res.body).to.be.eql(
{ name: 'Sugar', id: test.ingredient2 }
);
+
done();
});
});
@@ -1210,10 +1340,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 }
]);
+
done();
});
});
@@ -1235,9 +1368,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: 'Sugar', id: test.ingredient2 }
]);
+
done();
});
});
@@ -1248,10 +1384,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 }
]);
+
done();
});
});
@@ -1261,8 +1400,11 @@ 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();
});
});
@@ -1339,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();
});
@@ -1354,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();
});
});
@@ -1373,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();
});
});
@@ -1386,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();
});
});
@@ -1395,9 +1546,12 @@ 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');
+
done();
});
});
@@ -1406,10 +1560,13 @@ 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;
expect(res.body.text).to.equal('Page Note 1');
+
done();
});
});
@@ -1418,10 +1575,13 @@ 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);
expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND');
+
done();
});
});
@@ -1430,9 +1590,12 @@ 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');
+
done();
});
});
@@ -1441,8 +1604,11 @@ 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();
});
});
@@ -1451,9 +1617,12 @@ 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');
+
done();
});
});
@@ -1462,10 +1631,13 @@ 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;
expect(res.body.text).to.equal('Page Note 1');
+
done();
});
});
@@ -1474,8 +1646,11 @@ 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();
});
});
@@ -1487,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(/^\/.*/);
});
});
@@ -1497,11 +1673,14 @@ 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');
expect(res.body.error.status).to.equal(500);
expect(res.body.error.message).to.equal('This should not crash the app');
+
done();
});
});
@@ -1513,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();
});
});
@@ -1524,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);
});
});
@@ -1537,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();
});
});
@@ -1550,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();
});
});
@@ -1573,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 e267c2e86..701e170ba 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');
@@ -16,6 +21,7 @@ describe('RemoteConnector', function() {
port: remoteApp.get('port'),
connector: loopback.Remote
});
+
done();
});
},
@@ -43,6 +49,7 @@ describe('RemoteConnector', function() {
port: remoteApp.get('port'),
connector: loopback.Remote
});
+
done();
});
});
@@ -65,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 6dab48e08..bc6bd7950 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');
@@ -27,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 c3288d799..bf2bb9f96 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');
@@ -6,6 +11,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');
@@ -41,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));
});
@@ -61,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));
});
@@ -109,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',
diff --git a/test/replication.rest.test.js b/test/replication.rest.test.js
index 6316457f0..eb963e7b7 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;
@@ -109,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();
});
});
@@ -132,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();
});
});
@@ -145,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();
});
});
@@ -219,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
@@ -233,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();
});
});
@@ -243,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));
});
@@ -257,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();
});
});
@@ -273,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();
});
});
@@ -293,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();
});
});
@@ -310,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();
});
},
@@ -318,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();
});
}
@@ -335,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();
});
},
@@ -343,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();
});
}
@@ -456,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 }));
@@ -467,6 +505,7 @@ describe('Replication over REST', function() {
serverApp.listen(function() {
serverUrl = serverApp.get('url').replace(/\/+$/, '');
request = supertest(serverUrl);
+
done();
});
}
@@ -522,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) {
@@ -552,7 +595,9 @@ describe('Replication over REST', function() {
],
function(err, cars) {
if (err) return next(err);
+
serverCars = cars.map(carToString);
+
next();
});
}
@@ -569,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();
});
},
@@ -579,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
@@ -590,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);
});
@@ -607,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();
};
}
@@ -615,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 2606aaf53..83a88eb98 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('../');
@@ -46,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);
});
@@ -67,40 +73,30 @@ 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 +106,16 @@ 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 +124,18 @@ 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 +146,45 @@ 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() {
@@ -166,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);
@@ -182,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();
});
});
@@ -202,6 +231,7 @@ describe('Replication / Change APIs', function() {
function(cb) {
sourceModel.find(function(err, result) {
if (err) return cb(err);
+
sourceData = result;
cb();
});
@@ -209,6 +239,7 @@ describe('Replication / Change APIs', function() {
function(cb) {
targetModel.find(function(err, result) {
if (err) return cb(err);
+
targetData = result;
cb();
});
@@ -217,6 +248,7 @@ describe('Replication / Change APIs', function() {
if (err) return done(err);
assert.deepEqual(sourceData, targetData);
+
done();
});
}
@@ -227,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);
@@ -243,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) {
@@ -277,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();
});
}
@@ -309,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();
});
}
@@ -324,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();
});
});
@@ -339,6 +377,7 @@ describe('Replication / Change APIs', function() {
SourceModel.replicate(10, TargetModel, {})
.then(function() {
expect(diffSince).to.eql([10]);
+
done();
})
.catch(function(err) {
@@ -356,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();
});
});
@@ -374,6 +415,7 @@ describe('Replication / Change APIs', function() {
.then(function() {
expect(sourceSince).to.eql([1]);
expect(targetSince).to.eql([2]);
+
done();
})
.catch(function(err) {
@@ -396,7 +438,9 @@ describe('Replication / Change APIs', function() {
function getLastCp(next) {
SourceModel.currentCheckpoint(function(err, cp) {
if (err) return done(err);
+
lastCp = cp;
+
next();
});
},
@@ -411,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();
});
});
@@ -421,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();
});
}
@@ -440,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();
});
}
@@ -453,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();
});
}
@@ -461,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();
});
}
@@ -476,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();
});
});
@@ -644,6 +699,7 @@ describe('Replication / Change APIs', function() {
cb);
}
});
+
next();
},
replicateExpectingSuccess(),
@@ -679,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();
});
});
@@ -695,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();
});
});
@@ -706,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();
});
});
@@ -720,6 +781,7 @@ describe('Replication / Change APIs', function() {
id: test.model.id,
name: 'target update'
});
+
done();
});
});
@@ -738,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);
});
@@ -745,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();
});
});
@@ -767,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();
});
});
@@ -778,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();
});
});
@@ -789,6 +858,7 @@ describe('Replication / Change APIs', function() {
id: test.model.id,
name: 'target update'
});
+
done();
});
});
@@ -815,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();
});
});
@@ -836,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();
});
});
@@ -847,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();
});
});
@@ -858,6 +934,7 @@ describe('Replication / Change APIs', function() {
id: test.model.id,
name: 'source update'
});
+
done();
});
});
@@ -876,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);
});
@@ -883,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();
});
});
@@ -907,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);
});
});
@@ -914,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);
});
});
@@ -931,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);
});
@@ -940,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);
});
@@ -952,6 +1039,7 @@ describe('Replication / Change APIs', function() {
{ name: 'created' },
function(err, inst) {
if (err) return done(err);
+
assertChangeRecordedForId(inst.id, done);
});
});
@@ -959,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);
});
@@ -968,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);
});
});
@@ -978,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);
});
});
@@ -991,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);
});
});
@@ -1002,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);
});
});
@@ -1012,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);
});
@@ -1021,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);
});
});
@@ -1032,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
@@ -1041,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();
});
});
@@ -1056,6 +1159,7 @@ describe('Replication / Change APIs', function() {
SourceModel.create({ id: 'test-instance' }, function(err, result) {
sourceInstance = result;
sourceInstanceId = result.id;
+
next(err);
});
},
@@ -1094,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();
});
}
@@ -1361,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);
});
@@ -1393,6 +1500,7 @@ describe('Replication / Change APIs', function() {
debug('delete source instance', value);
sourceInstance.remove(function(err) {
sourceInstance = null;
+
next(err);
});
};
@@ -1400,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();
});
};
@@ -1430,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);
});
}
@@ -1450,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();
});
};
@@ -1467,6 +1582,7 @@ describe('Replication / Change APIs', function() {
var self = this;
fn(function(err) {
if (err) return cb(err);
+
bulkUpdate.call(self, data, cb);
});
@@ -1479,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 5757c44b6..2e372f0ea 100644
--- a/test/rest.middleware.test.js
+++ b/test/rest.middleware.test.js
@@ -1,11 +1,20 @@
+// 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() {
- 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) {
@@ -25,6 +34,7 @@ describe('loopback.rest', function() {
.del('/mymodels/' + inst.id)
.expect(200, function(err, res) {
expect(res.body.count).to.equal(1);
+
done();
});
});
@@ -36,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();
});
});
@@ -61,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();
});
});
@@ -94,14 +106,16 @@ 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();
});
});
});
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 +131,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 +156,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)
@@ -170,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);
@@ -187,7 +202,9 @@ describe('loopback.rest', function() {
.expect(200)
.end(function(err, res) {
if (err) return done(err);
+
expect(res.body).to.eql([]);
+
done();
});
});
@@ -198,7 +215,9 @@ describe('loopback.rest', function() {
.expect(200)
.end(function(err, res) {
if (err) return done(err);
+
expect(res.body).to.eql({});
+
done();
});
});
@@ -255,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();
});
});
@@ -268,27 +290,28 @@ 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();
});
app.use(loopback.rest());
@@ -321,35 +344,30 @@ 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);
+
User.login(credentials, cb);
});
}
@@ -398,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();
});
});
@@ -444,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 3053a0aff..50a5e0321 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');
@@ -8,9 +13,9 @@ 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) {
- // console.log(err, result);
assert(!err);
}
@@ -60,11 +65,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);
@@ -72,7 +76,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);
@@ -88,11 +91,25 @@ describe('role model', function() {
});
- it('should automatically generate role id', function() {
+ it('should not allow duplicate role name', function(done) {
+ Role.create({ name: 'userRole' }, function(err, role) {
+ if (err) return done(err);
- 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.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) {
+ 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);
@@ -104,7 +121,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);
@@ -121,13 +137,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);
});
@@ -167,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,
@@ -182,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: 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: null },
+ function(err, yes) {
assert(!err && !yes);
});
@@ -204,9 +234,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) {
@@ -228,6 +258,7 @@ describe('role model', function() {
password: 'jpass'
}, function(err, u) {
if (err) return done(err);
+
user = u;
User.create({
username: 'mary',
@@ -235,15 +266,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 = [
{
@@ -267,7 +301,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();
});
});
@@ -275,7 +311,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();
});
});
@@ -283,7 +321,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();
});
});
@@ -291,7 +331,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();
});
});
@@ -299,7 +341,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();
});
});
@@ -307,7 +351,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();
});
});
@@ -315,7 +361,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();
});
});
@@ -324,7 +372,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();
});
});
@@ -332,7 +382,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();
});
});
@@ -340,7 +392,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();
});
});
@@ -378,6 +432,7 @@ describe('role model', function() {
role[pluralName](function(err, models) {
assert(!err);
assert.equal(models.length, 1);
+
if (++runs === mappings.length) {
done();
}
@@ -399,6 +454,7 @@ describe('role model', function() {
assert.equal(users.length, 1);
assert.equal(users[0].id, user.id);
assert(User.find.calledWith(query));
+
done();
});
});
@@ -407,4 +463,29 @@ 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();
+ });
+ });
+ });
+ });
});
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 b2a92537b..753df249b 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');
@@ -7,6 +12,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);
@@ -19,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();
});
});
@@ -38,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();
});
});
@@ -50,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();
});
});
@@ -64,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();
});
});
@@ -80,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();
});
});
@@ -102,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();
});
});
@@ -117,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();
});
});
@@ -135,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();
});
});
@@ -146,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 8e2b14948..28e9650cc 100644
--- a/test/user.test.js
+++ b/test/user.test.js
@@ -1,12 +1,11 @@
+// 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;
-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,42 +18,57 @@ 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() {
- // 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();
+ 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' });
+
+ 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) {
- User.create(validCredentialsEmailVerified, done);
- });
- });
+ if (err) return done(err);
- afterEach(function(done) {
- loopback.User.app = defaultApp;
- User.destroyAll(function(err) {
- User.accessToken.destroyAll(done);
+ User.create(validCredentialsEmailVerified, done);
});
});
@@ -64,6 +78,7 @@ describe('User', function() {
assert(!err);
assert(user.id);
assert(user.email);
+
done();
});
});
@@ -72,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();
});
});
@@ -81,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();
});
});
@@ -97,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();
});
});
@@ -125,6 +145,7 @@ describe('User', function() {
User.create({email: 'c@d.com'}, function(err) {
assert(err);
+
done();
});
});
@@ -132,6 +153,7 @@ describe('User', function() {
it('Requires a valid email', function(done) {
User.create({email: 'foo@', password: '123'}, function(err) {
assert(err);
+
done();
});
});
@@ -140,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();
});
});
@@ -149,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();
});
});
@@ -160,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();
});
});
@@ -170,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();
});
});
@@ -181,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();
});
});
@@ -245,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();
});
});
@@ -258,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();
});
});
@@ -266,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();
});
});
@@ -290,6 +322,7 @@ describe('User', function() {
assert(accessToken.userId);
assert(accessToken.id);
assert.equal(accessToken.id.length, 64);
+
done();
});
});
@@ -297,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();
});
});
@@ -323,6 +357,7 @@ describe('User', function() {
assert(accessToken.id);
assert.equal(accessToken.ttl, 120);
assert.equal(accessToken.id.length, 64);
+
done();
});
});
@@ -341,6 +376,7 @@ describe('User', function() {
assert(accessToken.id);
assert.equal(accessToken.ttl, 120);
assert.equal(accessToken.id.length, 64);
+
done();
})
.catch(function(err) {
@@ -371,6 +407,7 @@ describe('User', function() {
assert.equal(accessToken.id.length, 64);
// Restore create access token
User.prototype.createAccessToken = createToken;
+
done();
});
});
@@ -401,6 +438,7 @@ describe('User', function() {
assert.equal(accessToken.scopes, 'default');
// Restore create access token
User.prototype.createAccessToken = createToken;
+
done();
});
});
@@ -412,6 +450,7 @@ describe('User', function() {
assert(err);
assert.equal(err.code, 'LOGIN_FAILED');
assert(!accessToken);
+
done();
});
});
@@ -420,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();
});
});
@@ -433,6 +474,7 @@ describe('User', function() {
User.login(incompleteCredentials, function(err, accessToken) {
assert(err);
assert.equal(err.code, 'USERNAME_EMAIL_REQUIRED');
+
done();
});
});
@@ -441,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();
});
});
@@ -457,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);
@@ -478,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();
});
});
@@ -494,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();
});
});
@@ -511,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();
});
});
@@ -527,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();
});
});
@@ -545,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();
});
});
@@ -578,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();
});
});
@@ -592,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();
});
});
@@ -600,6 +645,7 @@ describe('User', function() {
User.login(validCredentials, function(err, accessToken) {
assert(err);
assert.equal(err.code, 'LOGIN_FAILED_EMAIL_NOT_VERIFIED');
+
done();
});
});
@@ -612,6 +658,7 @@ describe('User', function() {
.catch(function(err) {
assert(err);
assert.equal(err.code, 'LOGIN_FAILED_EMAIL_NOT_VERIFIED');
+
done();
});
});
@@ -619,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();
});
});
@@ -627,6 +675,7 @@ describe('User', function() {
User.login(validCredentialsEmailVerified)
.then(function(accessToken) {
assertGoodToken(accessToken);
+
done();
})
.catch(function(err) {
@@ -641,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);
@@ -660,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();
});
});
@@ -679,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();
});
});
@@ -694,12 +742,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: ':',
+ });
+
+ AccessToken = app.registry.createModel('RealmAccessToken', {}, {
+ base: 'AccessToken',
+ });
- loopback.autoAttach();
+ 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'});
@@ -762,27 +817,18 @@ 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);
});
});
- 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);
assert.equal(err.code, 'REALM_REQUIRED');
+
done();
});
});
@@ -791,6 +837,7 @@ describe('User', function() {
User.login(credentialWithBadRealm, function(err, accessToken) {
assert(err);
assert.equal(err.code, 'LOGIN_FAILED');
+
done();
});
});
@@ -799,6 +846,7 @@ describe('User', function() {
User.login(credentialWithBadPass, function(err, accessToken) {
assert(err);
assert.equal(err.code, 'LOGIN_FAILED');
+
done();
});
});
@@ -807,6 +855,7 @@ describe('User', function() {
User.login(credentialWithRealm, function(err, accessToken) {
assertGoodToken(accessToken);
assert.equal(accessToken.userId, user1.id);
+
done();
});
});
@@ -815,6 +864,7 @@ describe('User', function() {
User.login(credentialRealmInUsername, function(err, accessToken) {
assertGoodToken(accessToken);
assert.equal(accessToken.userId, user1.id);
+
done();
});
});
@@ -823,16 +873,17 @@ describe('User', function() {
User.login(credentialRealmInEmail, function(err, accessToken) {
assertGoodToken(accessToken);
assert.equal(accessToken.userId, user1.id);
+
done();
});
});
describe('User.login with realmRequired but no realmDelimiter', function() {
- before(function() {
+ beforeEach(function() {
User.settings.realmDelimiter = undefined;
});
- after(function() {
+ afterEach(function() {
User.settings.realmDelimiter = ':';
});
@@ -840,6 +891,7 @@ describe('User', function() {
User.login(credentialWithRealm, function(err, accessToken) {
assertGoodToken(accessToken);
assert.equal(accessToken.userId, user1.id);
+
done();
});
});
@@ -849,6 +901,7 @@ describe('User', function() {
User.login(credentialRealmInEmail, function(err, accessToken) {
assert(err);
assert.equal(err.code, 'REALM_REQUIRED');
+
done();
});
});
@@ -893,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);
@@ -918,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);
});
};
@@ -935,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();
});
});
@@ -944,6 +996,7 @@ describe('User', function() {
u.hasPassword('bar')
.then(function(isMatch) {
assert(isMatch, 'password doesnt match');
+
done();
})
.catch(function(err) {
@@ -958,6 +1011,7 @@ describe('User', function() {
User.findById(user.id, function(err, uu) {
uu.hasPassword('b', function(err, isMatch) {
assert(isMatch);
+
done();
});
});
@@ -977,6 +1031,7 @@ describe('User', function() {
User.findById(user.id, function(err, uu) {
uu.hasPassword('baz2', function(err, isMatch) {
assert(isMatch);
+
done();
});
});
@@ -1011,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();
});
});
@@ -1021,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);
});
});
@@ -1042,13 +1096,13 @@ 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);
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) {
@@ -1062,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);
});
});
@@ -1085,6 +1137,7 @@ describe('User', function() {
user.verify(options, function(err, result) {
assert(result.email);
assert.equal(result.email.messageId, 'custom-header-value');
+
done();
});
});
@@ -1095,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);
});
});
@@ -1131,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();
});
});
@@ -1141,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);
});
});
@@ -1170,6 +1220,7 @@ describe('User', function() {
assert(err);
assert.equal(err.message, 'Fake error');
assert.equal(result, undefined);
+
done();
});
});
@@ -1180,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);
});
});
@@ -1204,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();
});
});
@@ -1214,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);
});
});
@@ -1237,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();
});
});
@@ -1247,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);
});
});
@@ -1270,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();
});
});
@@ -1280,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);
});
});
@@ -1303,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();
});
});
@@ -1313,13 +1360,18 @@ 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);
});
});
});
+ 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() {
@@ -1339,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);
});
});
@@ -1352,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);
});
}
@@ -1366,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);
@@ -1404,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);
@@ -1423,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);
@@ -1444,6 +1492,7 @@ describe('User', function() {
User.resetPassword({ }, function(err) {
assert(err);
assert.equal(err.code, 'EMAIL_REQUIRED');
+
done();
});
});
@@ -1456,6 +1505,7 @@ describe('User', function() {
.catch(function(err) {
assert(err);
assert.equal(err.code, 'EMAIL_REQUIRED');
+
done();
});
});
@@ -1465,6 +1515,7 @@ describe('User', function() {
assert(err);
assert.equal(err.code, 'EMAIL_NOT_FOUND');
assert.equal(err.statusCode, 404);
+
done();
});
});
@@ -1486,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();
});
});
@@ -1499,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();
});
});
@@ -1516,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();
});
});
@@ -1529,7 +1582,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() {
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..d554c2d48 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('../../');
@@ -125,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();
});
});
@@ -134,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();
});
});
@@ -146,6 +153,7 @@ module.exports = function defineModelTestsWithDataSource(options) {
assert(user.id);
assert(!err);
assert(!user.errors);
+
done();
});
});
@@ -165,6 +173,7 @@ module.exports = function defineModelTestsWithDataSource(options) {
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
+
done();
});
});
@@ -180,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();
});
});
@@ -191,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();
});
});
@@ -211,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();
});
});
@@ -225,6 +240,7 @@ module.exports = function defineModelTestsWithDataSource(options) {
assert.equal(user.id, 23);
assert.equal(user.first, 'michael');
assert.equal(user.last, 'jordan');
+
done();
});
});
@@ -242,6 +258,7 @@ module.exports = function defineModelTestsWithDataSource(options) {
.on('done', function() {
User.count({age: {gt: 99}}, function(err, count) {
assert.equal(count, 2);
+
done();
});
});