From e5b64c61430303f64d7750b18e02e746239dad31 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 15 Jul 2014 16:46:43 -0700 Subject: [PATCH 01/11] Upgrade to nodemailer 1.0.1 --- lib/connectors/mail.js | 7 ++++++- package.json | 3 ++- test/email.test.js | 5 +++-- test/user.test.js | 4 ++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/connectors/mail.js b/lib/connectors/mail.js index cd9df7b8..1e9b346a 100644 --- a/lib/connectors/mail.js +++ b/lib/connectors/mail.js @@ -58,7 +58,12 @@ MailConnector.prototype.setupTransport = function(setting) { var connector = this; connector.transports = connector.transports || []; connector.transportsIndex = connector.transportsIndex || {}; - var transport = mailer.createTransport(setting.type, setting); + + var transportModuleName = 'nodemailer-' + (setting.type || 'STUB').toLowerCase() + '-transport'; + var transportModule = require(transportModuleName); + + var transport = mailer.createTransport(transportModule(setting)); + connector.transportsIndex[setting.type] = transport; connector.transports.push(transport); } diff --git a/package.json b/package.json index f7496020..1e337f72 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "bcryptjs": "~2.0.1", "debug": "~1.0.4", "inflection": "~1.3.8", - "nodemailer": "~0.7.1", + "nodemailer": "~1.0.1", + "nodemailer-stub-transport": "~0.1.4", "uid2": "0.0.3", "underscore": "~1.6.0", "underscore.string": "~2.3.3" diff --git a/test/email.test.js b/test/email.test.js index 9a23ecb8..325330a0 100644 --- a/test/email.test.js +++ b/test/email.test.js @@ -24,7 +24,8 @@ describe('Email and SMTP', function () { }; MyEmail.send(options, function(err, mail) { - assert(mail.message); + assert(!err); + assert(mail.response); assert(mail.envelope); assert(mail.messageId); done(err); @@ -41,7 +42,7 @@ describe('Email and SMTP', function () { }); message.send(function (err, mail) { - assert(mail.message); + assert(mail.response); assert(mail.envelope); assert(mail.messageId); done(err); diff --git a/test/user.test.js b/test/user.test.js index 8b2e7b92..047ff831 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -443,11 +443,11 @@ describe('User', function(){ user.verify(options, function (err, result) { assert(result.email); - assert(result.email.message); + assert(result.email.response); assert(result.token); - assert(~result.email.message.indexOf('To: bar@bat.com')); + assert(~result.email.response.toString('utf-8').indexOf('To: bar@bat.com')); done(); }); }); From 30c35ca5ef020c04c5e526646ff2a0fc122fc474 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 16 Jul 2014 16:35:43 -0700 Subject: [PATCH 02/11] Move remoting metadata from juggler to loopback --- lib/models/model.js | 83 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/lib/models/model.js b/lib/models/model.js index 6fa5c312..1d917ca6 100644 --- a/lib/models/model.js +++ b/lib/models/model.js @@ -184,12 +184,17 @@ Model.setup = function () { // resolve relation functions sharedClass.resolve(function resolver(define) { var relations = ModelCtor.relations; - if(!relations) return; + if (!relations) { + return; + } // get the relations - for(var relationName in relations) { + for (var relationName in relations) { var relation = relations[relationName]; - if(relation.type === 'belongsTo') { + if (relation.type === 'belongsTo') { ModelCtor.belongsToRemoting(relationName, relation, define) + } else if (relation.type === 'hasMany') { + ModelCtor.hasManyRemoting(relationName, relation, define); + ModelCtor.scopeRemoting(relationName, relation, define); } else { ModelCtor.scopeRemoting(relationName, relation, define); } @@ -342,6 +347,78 @@ Model.belongsToRemoting = function(relationName, relation, define) { }, fn); } +Model.hasManyRemoting = function (relationName, relation, define) { + var findByIdFunc = this.prototype['__findById__' + relationName]; + define('__findById__' + relationName, { + isStatic: false, + http: {verb: 'get', path: '/' + relationName + '/:fk'}, + accepts: {arg: 'fk', type: 'any', + description: 'Foreign key for ' + relationName, required: true, + http: {source: 'path'}}, + description: 'Find a related item by id for ' + relationName, + returns: {arg: 'result', type: relation.modelTo.modelName, root: true} + }, findByIdFunc); + + var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; + define('__destroyById__' + relationName, { + isStatic: false, + http: {verb: 'delete', path: '/' + relationName + '/:fk'}, + accepts: {arg: 'fk', type: 'any', + description: 'Foreign key for ' + relationName, required: true, + http: {source: 'path'}}, + description: 'Delete a related item by id for ' + relationName, + returns: {} + }, destroyByIdFunc); + + var updateByIdFunc = this.prototype['__updateById__' + relationName]; + define('__updateById__' + relationName, { + isStatic: false, + http: {verb: 'put', path: '/' + relationName + '/:fk'}, + accepts: {arg: 'fk', type: 'any', + description: 'Foreign key for ' + relationName, required: true, + http: {source: 'path'}}, + description: 'Update a related item by id for ' + relationName, + returns: {arg: 'result', type: relation.modelTo.modelName, root: true} + }, updateByIdFunc); + + if (relation.modelThrough) { + var addFunc = this.prototype['__link__' + relationName]; + define('__link__' + relationName, { + isStatic: false, + http: {verb: 'put', path: '/' + relationName + '/rel/:fk'}, + accepts: {arg: 'fk', type: 'any', + description: 'Foreign key for ' + relationName, required: true, + http: {source: 'path'}}, + description: 'Add a related item by id for ' + relationName, + returns: {arg: relationName, type: relation.modelThrough.modelName, root: true} + }, addFunc); + + var removeFunc = this.prototype['__unlink__' + relationName]; + define('__unlink__' + relationName, { + isStatic: false, + http: {verb: 'delete', path: '/' + relationName + '/rel/:fk'}, + accepts: {arg: 'fk', type: 'any', + description: 'Foreign key for ' + relationName, required: true, + http: {source: 'path'}}, + description: 'Remove the ' + relationName + ' relation to an item by id', + returns: {} + }, removeFunc); + + // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? + // true --> 200 and false --> 404? + var existsFunc = this.prototype['__exists__' + relationName]; + define('__exists__' + relationName, { + isStatic: false, + http: {verb: 'head', path: '/' + relationName + '/rel/:fk'}, + accepts: {arg: 'fk', type: 'any', + description: 'Foreign key for ' + relationName, required: true, + http: {source: 'path'}}, + description: 'Check the existence of ' + relationName + ' relation to an item by id', + returns: {} + }, existsFunc); + } +}; + Model.scopeRemoting = function(relationName, relation, define) { var toModelName = relation.modelTo.modelName; From 4f7a9869e0ac3e20e1dce86a8d175ea1654088c8 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 18 Jul 2014 22:48:07 -0700 Subject: [PATCH 03/11] Add descriptions for custom methods on user model --- lib/models/user.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/user.js b/lib/models/user.js index 2c84298e..d1bf7e83 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -451,6 +451,7 @@ User.setup = function () { loopback.remoteMethod( UserModel.login, { + description: 'Login a user with username/email and password', accepts: [ {arg: 'credentials', type: 'object', required: true, http: {source: 'body'}}, {arg: 'include', type: 'string', http: {source: 'query' }, description: @@ -471,6 +472,7 @@ User.setup = function () { loopback.remoteMethod( UserModel.logout, { + description: 'Logout a user with access token', accepts: [ {arg: 'access_token', type: 'string', required: true, http: function(ctx) { var req = ctx && ctx.req; @@ -490,6 +492,7 @@ User.setup = function () { loopback.remoteMethod( UserModel.confirm, { + description: 'Confirm a user registration with email verification token', accepts: [ {arg: 'uid', type: 'string', required: true}, {arg: 'token', type: 'string', required: true}, @@ -502,6 +505,7 @@ User.setup = function () { loopback.remoteMethod( UserModel.resetPassword, { + description: 'Reset password for a user with email', accepts: [ {arg: 'options', type: 'object', required: true, http: {source: 'body'}} ], From 90094e5e868d261354a039f37a1de0bfb7b1dddd Mon Sep 17 00:00:00 2001 From: Jaka Hudoklin Date: Sat, 19 Jul 2014 14:18:21 +0200 Subject: [PATCH 04/11] Validate username uniqueness Signed-off-by: Jaka Hudoklin --- lib/models/user.js | 4 +++- test/user.test.js | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/models/user.js b/lib/models/user.js index d1bf7e83..357c70f2 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -527,10 +527,12 @@ User.setup = function () { UserModel.email = require('./email'); UserModel.accessToken = require('./access-token'); - UserModel.validatesUniquenessOf('email', {message: 'Email already exists'}); + // email validation regex var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + UserModel.validatesUniquenessOf('email', {message: 'Email already exists'}); UserModel.validatesFormatOf('email', {with: re, message: 'Must provide a valid email'}); + UserModel.validatesUniquenessOf('username', {message: 'User already exists'}); return UserModel; } diff --git a/test/user.test.js b/test/user.test.js index 8b2e7b92..63c9a606 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -109,6 +109,15 @@ describe('User', function(){ }); }); }); + + it('Requires a unique username', function(done) { + 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(); + }); + }); + }); it('Requires a password to login with basic auth', function(done) { User.create({email: 'b@c.com'}, function (err) { From eb5ef04b6a3989e5877ed88378a18b8748d8f440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 21 Jul 2014 16:56:46 +0200 Subject: [PATCH 05/11] Remove `loopback.compat.usePluralNamesForRemoting` The `usePluralNamesForRemoting` was added in January 2014 for users upgrading from LoopBack 1.5 or older. --- lib/application.js | 1 - lib/compat.js | 56 ---------------------------------------- lib/connectors/remote.js | 1 - lib/loopback.js | 5 ---- lib/models/model.js | 7 +++-- test/app.test.js | 22 ---------------- test/model.test.js | 31 ---------------------- 7 files changed, 3 insertions(+), 120 deletions(-) delete mode 100644 lib/compat.js diff --git a/lib/application.js b/lib/application.js index 7ccfcbb0..97bb1989 100644 --- a/lib/application.js +++ b/lib/application.js @@ -4,7 +4,6 @@ var DataSource = require('loopback-datasource-juggler').DataSource , registry = require('./registry') - , compat = require('./compat') , assert = require('assert') , fs = require('fs') , extend = require('util')._extend diff --git a/lib/compat.js b/lib/compat.js deleted file mode 100644 index 9fe324f7..00000000 --- a/lib/compat.js +++ /dev/null @@ -1,56 +0,0 @@ -var assert = require('assert'); - -/** - * Compatibility layer allowing applications based on an older LoopBack version - * to work with newer versions with minimum changes involved. - * - * You should not use it unless migrating from an older version of LoopBack. - */ - -var compat = exports; - -/** - * LoopBack versions pre-1.6 use plural model names when registering shared - * classes with strong-remoting. As the result, strong-remoting use method names - * like `Users.create` for the javascript methods like `User.create`. - * This has been fixed in v1.6, LoopBack consistently uses the singular - * form now. - * - * Turn this option on to enable the old behaviour. - * - * - `app.remotes()` and `app.remoteObjects()` will be indexed using - * plural names (Users instead of User). - * - * - Remote hooks must use plural names for the class name, i.e - * `Users.create` instead of `User.create`. This is transparently - * handled by `Model.beforeRemote()` and `Model.afterRemote()`. - * - * @type {boolean} - * @deprecated Your application should not depend on the way how loopback models - * and strong-remoting are wired together. It if does, you should update - * it to use singular model names. - */ - -compat.usePluralNamesForRemoting = false; - -/** - * Get the class name to use with strong-remoting. - * @param {function} Ctor Model class (constructor), e.g. `User` - * @return {string} Singular or plural name, depending on the value - * of `compat.usePluralNamesForRemoting` - * @internal - */ - -compat.getClassNameForRemoting = function(Ctor) { - assert( - typeof(Ctor) === 'function', - 'compat.getClassNameForRemoting expects a constructor as the argument'); - - if (compat.usePluralNamesForRemoting) { - assert(Ctor.pluralModelName, - 'Model must have a "pluralModelName" property in compat mode'); - return Ctor.pluralModelName; - } - - return Ctor.modelName; -}; diff --git a/lib/connectors/remote.js b/lib/connectors/remote.js index 31b93273..26beb4a5 100644 --- a/lib/connectors/remote.js +++ b/lib/connectors/remote.js @@ -4,7 +4,6 @@ var assert = require('assert'); var remoting = require('strong-remoting'); -var compat = require('../compat'); var DataAccessObject = require('loopback-datasource-juggler/lib/dao'); /** diff --git a/lib/loopback.js b/lib/loopback.js index 40eca19c..1b8168f7 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -38,11 +38,6 @@ loopback.version = require('../package.json').version; loopback.mime = express.mime; -/*! - * Compatibility layer, intentionally left undocumented. - */ -loopback.compat = require('./compat'); - /*! * Create an loopback application. * diff --git a/lib/models/model.js b/lib/models/model.js index 6fa5c312..6893e325 100644 --- a/lib/models/model.js +++ b/lib/models/model.js @@ -2,7 +2,6 @@ * Module Dependencies. */ var registry = require('../registry'); -var compat = require('../compat'); var assert = require('assert'); var SharedClass = require('strong-remoting').SharedClass; @@ -87,7 +86,7 @@ Model.setup = function () { // create a sharedClass var sharedClass = ModelCtor.sharedClass = new SharedClass( - compat.getClassNameForRemoting(ModelCtor), + ModelCtor.modelName, ModelCtor, options.remoting ); @@ -152,7 +151,7 @@ Model.setup = function () { var self = this; if(this.app) { var remotes = this.app.remotes(); - var className = compat.getClassNameForRemoting(self); + var className = self.modelName; remotes.before(className + '.' + name, function (ctx, next) { fn(ctx, ctx.result, next); }); @@ -169,7 +168,7 @@ Model.setup = function () { var self = this; if(this.app) { var remotes = this.app.remotes(); - var className = compat.getClassNameForRemoting(self); + var className = self.modelName; remotes.after(className + '.' + name, function (ctx, next) { fn(ctx, ctx.result, next); }); diff --git a/test/app.test.js b/test/app.test.js index 5c7b62cd..8bed1cb2 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -57,28 +57,6 @@ describe('app', function() { request(app).get('/colors').expect(200, done); }); }); - - describe('in compat mode', function() { - before(function() { - loopback.compat.usePluralNamesForRemoting = true; - }); - after(function() { - loopback.compat.usePluralNamesForRemoting = false; - }); - - it('uses plural name as shared class name', function() { - var Color = db.createModel('color', {name: String}); - app.model(Color); - var classes = app.remotes().classes().map(function(c) {return c.name}); - expect(classes).to.contain('colors'); - }); - - it('uses plural name as app.remoteObjects() key', function() { - var Color = db.createModel('color', {name: String}); - app.model(Color); - expect(app.remoteObjects()).to.eql({ colors: Color }); - }); - }); }); describe('app.model(name, config)', function () { diff --git a/test/model.test.js b/test/model.test.js index 145f0185..3c544875 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -270,37 +270,6 @@ describe.onServer('Remote Methods', function(){ }); }) - describe('in compat mode', function() { - before(function() { - loopback.compat.usePluralNamesForRemoting = true; - }); - after(function() { - loopback.compat.usePluralNamesForRemoting = false; - }); - - it('correctly install before/after hooks', function(done) { - var hooksCalled = []; - - User.beforeRemote('**', function(ctx, user, next) { - hooksCalled.push('beforeRemote'); - next(); - }); - - User.afterRemote('**', function(ctx, user, next) { - hooksCalled.push('afterRemote'); - next(); - }); - - request(app).get('/users') - .expect(200, function(err, res) { - if (err) return done(err); - expect(hooksCalled, 'hooks called') - .to.eql(['beforeRemote', 'afterRemote']); - done(); - }); - }); - }); - describe('Model.hasMany(Model)', function() { it("Define a one to many relationship", function(done) { var Book = dataSource.createModel('book', {title: String, author: String}); From c7bca9da78375bf7ba664c4556ca06c3a21471bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 22 Jul 2014 15:08:23 +0200 Subject: [PATCH 06/11] Remove `app.docs()` The swagger integration was moved to loopback-explorer. --- lib/application.js | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/lib/application.js b/lib/application.js index 97bb1989..84884098 100644 --- a/lib/application.js +++ b/lib/application.js @@ -9,7 +9,6 @@ var DataSource = require('loopback-datasource-juggler').DataSource , extend = require('util')._extend , _ = require('underscore') , RemoteObjects = require('strong-remoting') - , swagger = require('strong-remoting/ext/swagger') , stringUtils = require('underscore.string') , path = require('path'); @@ -266,34 +265,6 @@ app.remoteObjects = function () { return result; } -/** - * Enable swagger REST API documentation. - * - * **Note**: This method is deprecated. Use [loopback-explorer](http://npmjs.org/package/loopback-explorer) instead. - * - * **Options** - * - * - `basePath` The basepath for your API - eg. 'http://localhost:3000'. - * - * **Example** - * - * ```js - * // enable docs - * app.docs({basePath: 'http://localhost:3000'}); - * ``` - * - * Run your app then navigate to - * [the API explorer](http://petstore.swagger.wordnik.com/). - * Enter your API basepath to view your generated docs. - * - * @deprecated - */ - -app.docs = function (options) { - var remotes = this.remotes(); - swagger(remotes, options); -} - /*! * Get a handler of the specified type from the handler cache. */ From bba58a73d5e778d8e3eca201e64ee1779c027a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 22 Jul 2014 15:40:36 +0200 Subject: [PATCH 07/11] express-middleware: improve error message Include the flag `--save` in the npm instructions, so that the missing module is both installed and saved in package dependencies. --- lib/express-middleware.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/express-middleware.js b/lib/express-middleware.js index cc67278c..bc316769 100644 --- a/lib/express-middleware.js +++ b/lib/express-middleware.js @@ -13,8 +13,9 @@ function safeRequire(m) { function createMiddlewareNotInstalled(memberName, moduleName) { return function () { - throw new Error('The middleware loopback.' + memberName + ' is not installed.\n' + - 'Please run `npm install ' + moduleName + '` to fix the problem.'); + var msg = 'The middleware loopback.' + memberName + ' is not installed.\n' + + 'Run `npm install --save ' + moduleName + '` to fix the problem.'; + throw new Error(msg); }; } From 643293cc2534f697d534732ce817a115479629c1 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sun, 20 Jul 2014 11:52:12 -0700 Subject: [PATCH 08/11] Set up the base model based on the connector types --- lib/registry.js | 13 +++++++++++++ test/data-source.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib/registry.js b/lib/registry.js index 8b581066..6e0289e2 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -239,6 +239,19 @@ registry.createDataSource = function (name, options) { var self = this; var ds = new DataSource(name, options, self.modelBuilder); ds.createModel = function (name, properties, settings) { + settings = settings || {}; + var BaseModel = settings.base || settings.super; + if (!BaseModel) { + // Check the connector types + var connectorTypes = ds.connector && ds.connector.getTypes(); + if (Array.isArray(connectorTypes) && connectorTypes.indexOf('db') !== -1) { + // Only set up the base model to PersistedModel if the connector is DB + BaseModel = self.PersistedModel; + } else { + BaseModel = self.Model; + } + settings.base = BaseModel; + } var ModelCtor = self.createModel(name, properties, settings); ModelCtor.attachTo(ds); return ModelCtor; diff --git a/test/data-source.test.js b/test/data-source.test.js index a1e0e126..b8354c04 100644 --- a/test/data-source.test.js +++ b/test/data-source.test.js @@ -33,6 +33,34 @@ describe('DataSource', function() { assert.isFunc(Color.prototype, 'updateAttributes'); assert.isFunc(Color.prototype, 'reload'); }); + + it("should honor settings.base", function() { + var Base = memory.createModel('base'); + var Color = memory.createModel('color', {name: String}, {base: Base}); + assert.equal(Color.super_, Base); + }); + + it("should use loopback.PersistedModel as the base for DBs", function() { + var Color = memory.createModel('color', {name: String}); + assert.equal(Color.super_, loopback.PersistedModel); + }); + + it("should use loopback.Model as the base for non DBs", function() { + // Mock up a non-DB connector + var Connector = function() { + }; + Connector.prototype.getTypes = function() { + return ['rest']; + }; + + var ds = loopback.createDataSource({ + connector: new Connector() + }); + + var Color = ds.createModel('color', {name: String}); + assert.equal(Color.super_, loopback.Model); + }); + }); describe.skip('PersistedModel Methods', function() { From 21b8609ee23e8c0113bf2086235fa6490d25681d Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 16 Jul 2014 22:42:05 -0700 Subject: [PATCH 09/11] Report error for User.confirm() See https://github.com/strongloop/loopback/issues/377 --- lib/models/user.js | 13 +++++-- test/user.test.js | 90 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/lib/models/user.js b/lib/models/user.js index e1518bd4..8b47b510 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -358,18 +358,25 @@ User.confirm = function (uid, token, redirect, fn) { if(err) { fn(err); } else { - if(user.verificationToken === token) { + if(user && user.verificationToken === token) { user.verificationToken = undefined; user.emailVerified = true; user.save(function (err) { if(err) { - fn(err) + fn(err); } else { fn(); } }); } else { - fn(new Error('invalid token')); + if (user) { + err = new Error('Invalid token: ' + token); + err.statusCode = 400; + } else { + err = new Error('User not found: ' + uid); + err.statusCode = 404; + } + fn(err); } } }); diff --git a/test/user.test.js b/test/user.test.js index 8b2e7b92..1b6785c8 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -426,7 +426,7 @@ describe('User', function(){ }); describe('Verification', function(){ - + describe('user.verify(options, fn)', function(){ it('Verify a user\'s email address', function(done) { User.afterRemote('create', function(ctx, user, next) { @@ -445,8 +445,6 @@ describe('User', function(){ assert(result.email); assert(result.email.message); assert(result.token); - - assert(~result.email.message.indexOf('To: bar@bat.com')); done(); }); @@ -462,13 +460,15 @@ describe('User', function(){ }); }); }); - - describe('User.confirm(options, fn)', function(){ - it('Confirm a user verification', function(done) { - User.afterRemote('create', function(ctx, user, next) { + + describe('User.confirm(options, fn)', function () { + var options; + + function testConfirm(testFunc, done) { + User.afterRemote('create', function (ctx, user, next) { assert(user, 'afterRemote should include result'); - - var options = { + + options = { type: 'email', to: user.email, from: 'noreply@myapp.org', @@ -476,29 +476,73 @@ describe('User', function(){ protocol: ctx.req.protocol, host: ctx.req.get('host') }; - + user.verify(options, function (err, result) { - if(err) return done(err); - - request(app) - .get('/users/confirm?uid=' + result.uid + '&token=' + encodeURIComponent(result.token) + '&redirect=' + encodeURIComponent(options.redirect)) - .expect(302) - .expect('location', options.redirect) - .end(function(err, res){ - if(err) return done(err); - done(); - }); + if (err) { + return done(err); + } + testFunc(result, done); }); }); - + request(app) .post('/users') .expect('Content-Type', /json/) .expect(302) .send({email: 'bar@bat.com', password: 'bar'}) - .end(function(err, res){ - if(err) return done(err); + .end(function (err, res) { + if (err) { + return done(err); + } }); + } + + it('Confirm a user verification', function (done) { + testConfirm(function (result, done) { + request(app) + .get('/users/confirm?uid=' + (result.uid ) + + '&token=' + encodeURIComponent(result.token) + + '&redirect=' + encodeURIComponent(options.redirect)) + .expect(302) + .end(function (err, res) { + if (err) { + return done(err); + } + done(); + }); + }, done); + }); + + it('Report error for invalid user id during verification', function (done) { + testConfirm(function (result, done) { + request(app) + .get('/users/confirm?uid=' + (result.uid + '_invalid') + + '&token=' + encodeURIComponent(result.token) + + '&redirect=' + encodeURIComponent(options.redirect)) + .expect(404) + .end(function (err, res) { + if (err) { + return done(err); + } + assert(res.body.error); + done(); + }); + }, done); + }); + + it('Report error for invalid token during verification', function (done) { + testConfirm(function (result, done) { + request(app) + .get('/users/confirm?uid=' + result.uid + + '&token=' + encodeURIComponent(result.token) + '_invalid' + + '&redirect=' + encodeURIComponent(options.redirect)) + .expect(400) + .end(function (err, res) { + if (err) return done(err); + assert(res.body.error); + done(); + }); + }, done); }); }); }); From 7337f0a0de01041dbac7040b4a7a016bb0f77253 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 22 Jul 2014 10:57:42 -0700 Subject: [PATCH 10/11] Enhance the base model assertions --- test/data-source.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/data-source.test.js b/test/data-source.test.js index b8354c04..113c3025 100644 --- a/test/data-source.test.js +++ b/test/data-source.test.js @@ -37,12 +37,14 @@ describe('DataSource', function() { it("should honor settings.base", function() { var Base = memory.createModel('base'); var Color = memory.createModel('color', {name: String}, {base: Base}); - assert.equal(Color.super_, Base); + assert(Color.prototype instanceof Base); + assert.equal(Color.base, Base); }); it("should use loopback.PersistedModel as the base for DBs", function() { var Color = memory.createModel('color', {name: String}); - assert.equal(Color.super_, loopback.PersistedModel); + assert(Color.prototype instanceof loopback.PersistedModel); + assert.equal(Color.base, loopback.PersistedModel); }); it("should use loopback.Model as the base for non DBs", function() { @@ -58,7 +60,8 @@ describe('DataSource', function() { }); var Color = ds.createModel('color', {name: String}); - assert.equal(Color.super_, loopback.Model); + assert(Color.prototype instanceof loopback.Model); + assert.equal(Color.base, loopback.Model); }); }); From 8687267f668a5e2c57094b025ab3dec19d89cf1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 22 Jul 2014 20:37:38 +0200 Subject: [PATCH 11/11] 2.0.0 --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1e337f72..05fd3e78 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "mobile", "mBaaS" ], - "version": "2.0.0-beta7", + "version": "2.0.0", "scripts": { "test": "grunt mocha-and-karma" }, @@ -36,7 +36,7 @@ "canonical-json": "0.0.4", "ejs": "~1.0.0", "express": "4.x", - "strong-remoting": "~2.0.0-beta5", + "strong-remoting": "^2.0.0", "bcryptjs": "~2.0.1", "debug": "~1.0.4", "inflection": "~1.3.8", @@ -47,7 +47,7 @@ "underscore.string": "~2.3.3" }, "peerDependencies": { - "loopback-datasource-juggler": "~2.0.0-beta3" + "loopback-datasource-juggler": "^2.0.0" }, "devDependencies": { "browserify": "~4.2.1", @@ -72,7 +72,7 @@ "karma-phantomjs-launcher": "~0.1.4", "karma-script-launcher": "~0.1.0", "loopback-boot": "^1.1.0", - "loopback-datasource-juggler": "~2.0.0-beta3", + "loopback-datasource-juggler": "^2.0.0", "loopback-testing": "~0.2.0", "mocha": "~1.20.1", "serve-favicon": "~2.0.1",