Merge branch 'release/2.0.0' into production
This commit is contained in:
commit
a3720bc05f
|
@ -4,13 +4,11 @@
|
|||
|
||||
var DataSource = require('loopback-datasource-juggler').DataSource
|
||||
, registry = require('./registry')
|
||||
, compat = require('./compat')
|
||||
, assert = require('assert')
|
||||
, fs = require('fs')
|
||||
, extend = require('util')._extend
|
||||
, _ = require('underscore')
|
||||
, RemoteObjects = require('strong-remoting')
|
||||
, swagger = require('strong-remoting/ext/swagger')
|
||||
, stringUtils = require('underscore.string')
|
||||
, path = require('path');
|
||||
|
||||
|
@ -267,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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
@ -184,12 +183,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 +346,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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -451,6 +458,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 +479,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 +499,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 +512,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'}}
|
||||
],
|
||||
|
@ -523,10 +534,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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
11
package.json
11
package.json
|
@ -26,7 +26,7 @@
|
|||
"mobile",
|
||||
"mBaaS"
|
||||
],
|
||||
"version": "2.0.0-beta7",
|
||||
"version": "2.0.0",
|
||||
"scripts": {
|
||||
"test": "grunt mocha-and-karma"
|
||||
},
|
||||
|
@ -36,17 +36,18 @@
|
|||
"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",
|
||||
"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"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"loopback-datasource-juggler": "~2.0.0-beta3"
|
||||
"loopback-datasource-juggler": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "~4.2.1",
|
||||
|
@ -71,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",
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -33,6 +33,37 @@ 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(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(Color.prototype instanceof loopback.PersistedModel);
|
||||
assert.equal(Color.base, 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(Color.prototype instanceof loopback.Model);
|
||||
assert.equal(Color.base, loopback.Model);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe.skip('PersistedModel Methods', function() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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) {
|
||||
|
@ -426,7 +435,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) {
|
||||
|
@ -443,11 +452,9 @@ 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();
|
||||
});
|
||||
});
|
||||
|
@ -462,13 +469,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 +485,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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue