Merge branch 'release/2.8.8' into production
This commit is contained in:
commit
0417e272e8
54
CHANGES.md
54
CHANGES.md
|
@ -1,3 +1,27 @@
|
|||
2015-01-07, Version 2.8.8
|
||||
=========================
|
||||
|
||||
* Fix context middleware to preserve domains (Pham Anh Tuan)
|
||||
|
||||
* Additional password reset unit tests for API and REST - strongloop/loopback#944 (Ron Edgecomb)
|
||||
|
||||
* Small formatting update to have consistency with identical logic in other areas. - strongloop/loopback#944 (Ron Edgecomb)
|
||||
|
||||
* Simplify the API test for invalidCredentials (removed create), move above REST calls for better grouping of tests - strongloop/loopback#944 (Ron Edgecomb)
|
||||
|
||||
* Force request to send body as string, this ensures headers aren't automatically set to application/json - strongloop/loopback#944 (Ron Edgecomb)
|
||||
|
||||
* Ensure error checking logic is in place for all REST calls, expand formatting for consistency with existing instances. - strongloop/loopback#944 (Ron Edgecomb)
|
||||
|
||||
* Correct invalidCredentials so that it differs from validCredentialsEmailVerified, unit test now passes as desired. - strongloop/loopback#944 (Ron Edgecomb)
|
||||
|
||||
* Update to demonstrate unit test is actually failing due to incorrect values of invalidCredentials - strongloop/loopback#944 (Ron Edgecomb)
|
||||
|
||||
* fix jscs warning (Clark Wang)
|
||||
|
||||
* fix nestRemoting is nesting hooks from other relations (Clark Wang)
|
||||
|
||||
|
||||
2015-01-06, Version 2.8.7
|
||||
=========================
|
||||
|
||||
|
@ -671,6 +695,10 @@
|
|||
|
||||
* Enhance the error message (Raymond Feng)
|
||||
|
||||
|
||||
2014-07-16, Version 2.0.0-beta7
|
||||
===============================
|
||||
|
||||
* Bump version (Raymond Feng)
|
||||
|
||||
* 2.0.0-beta6 (Miroslav Bajtoš)
|
||||
|
@ -811,13 +839,6 @@
|
|||
2014-07-16, Version 1.10.0
|
||||
==========================
|
||||
|
||||
|
||||
|
||||
2014-07-16, Version 2.0.0-beta7
|
||||
===============================
|
||||
|
||||
* Bump version (Raymond Feng)
|
||||
|
||||
* Remove unused dep (Raymond Feng)
|
||||
|
||||
* Bump version and update deps (Raymond Feng)
|
||||
|
@ -1264,14 +1285,6 @@
|
|||
|
||||
* 2.0.0-beta1 (Ritchie Martori)
|
||||
|
||||
* Bump version (Raymond Feng)
|
||||
|
||||
* Add postgresql to the keywords (Raymond Feng)
|
||||
|
||||
* updated package.json with SOAP and framework keywords (altsang)
|
||||
|
||||
* updated package.json with keywords and updated description (Raymond Feng)
|
||||
|
||||
* Make app.datasources unique per app instance (Miroslav Bajtoš)
|
||||
|
||||
* Add RC version (Ritchie Martori)
|
||||
|
@ -1337,11 +1350,6 @@
|
|||
* Add Change model (Ritchie Martori)
|
||||
|
||||
|
||||
2014-05-27, Version 1.8.4
|
||||
=========================
|
||||
|
||||
|
||||
|
||||
2014-05-27, Version 1.8.5
|
||||
=========================
|
||||
|
||||
|
@ -1353,8 +1361,14 @@
|
|||
|
||||
* updated package.json with keywords and updated description (Raymond Feng)
|
||||
|
||||
|
||||
2014-05-27, Version 1.8.4
|
||||
=========================
|
||||
|
||||
* Add more keywords (Raymond Feng)
|
||||
|
||||
* Bump version (Raymond Feng)
|
||||
|
||||
* app: flatten model config (Miroslav Bajtoš)
|
||||
|
||||
* Fix the test for mocha 1.19.0 (Raymond Feng)
|
||||
|
|
12
lib/model.js
12
lib/model.js
|
@ -654,7 +654,7 @@ Model.nestRemoting = function(relationName, options, cb) {
|
|||
opts.returns = [].concat(method.returns || []);
|
||||
opts.description = method.description;
|
||||
opts.rest = extend({}, method.rest || {});
|
||||
opts.rest.delegateTo = method.name;
|
||||
opts.rest.delegateTo = method;
|
||||
|
||||
opts.http = [];
|
||||
var routes = [].concat(method.http || []);
|
||||
|
@ -718,18 +718,18 @@ Model.nestRemoting = function(relationName, options, cb) {
|
|||
|
||||
sharedClass.methods().forEach(function(method) {
|
||||
var delegateTo = method.rest && method.rest.delegateTo;
|
||||
if (delegateTo) {
|
||||
if (delegateTo && delegateTo.ctor == relation.modelTo) {
|
||||
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
|
||||
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
|
||||
var m = method.isStatic ? method.name : 'prototype.' + method.name;
|
||||
if (before && before[delegateTo]) {
|
||||
if (before && before[delegateTo.name]) {
|
||||
self.beforeRemote(m, function(ctx, result, next) {
|
||||
before[delegateTo]._listeners.call(null, ctx, next);
|
||||
before[delegateTo.name]._listeners.call(null, ctx, next);
|
||||
});
|
||||
}
|
||||
if (after && after[delegateTo]) {
|
||||
if (after && after[delegateTo.name]) {
|
||||
self.afterRemote(m, function(ctx, result, next) {
|
||||
after[delegateTo]._listeners.call(null, ctx, next);
|
||||
after[delegateTo.name]._listeners.call(null, ctx, next);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback",
|
||||
"version": "2.8.7",
|
||||
"version": "2.8.8",
|
||||
"description": "LoopBack: Open Source Framework for Node.js",
|
||||
"homepage": "http://loopback.io",
|
||||
"keywords": [
|
||||
|
@ -102,6 +102,6 @@
|
|||
"url": "https://github.com/strongloop/loopback/blob/master/LICENSE"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sl-blip": "http://blip.strongloop.com/loopback@2.8.7"
|
||||
"sl-blip": "http://blip.strongloop.com/loopback@2.8.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ var loopback = require('../../lib/loopback');
|
|||
var juggler = require('loopback-datasource-juggler');
|
||||
var remoting = require('strong-remoting');
|
||||
var cls = require('continuation-local-storage');
|
||||
var domain = require('domain');
|
||||
|
||||
module.exports = context;
|
||||
|
||||
|
@ -44,6 +45,13 @@ function context(options) {
|
|||
var scope = options.name || name;
|
||||
var enableHttpContext = options.enableHttpContext || false;
|
||||
var ns = createContext(scope);
|
||||
|
||||
var currentDomain = process.domain = domain.create();
|
||||
currentDomain.oldBind = currentDomain.bind;
|
||||
currentDomain.bind = function(callback, context) {
|
||||
return currentDomain.oldBind(ns.bind(callback, context), context);
|
||||
};
|
||||
|
||||
// Return the middleware
|
||||
return function contextHandler(req, res, next) {
|
||||
if (req.loopbackContext) {
|
||||
|
@ -53,13 +61,19 @@ function context(options) {
|
|||
// Bind req/res event emitters to the given namespace
|
||||
ns.bindEmitter(req);
|
||||
ns.bindEmitter(res);
|
||||
|
||||
currentDomain.add(req);
|
||||
currentDomain.add(res);
|
||||
|
||||
// Create namespace for the request context
|
||||
ns.run(function processRequestInContext(context) {
|
||||
// Run the code in the context of the namespace
|
||||
if (enableHttpContext) {
|
||||
ns.set('http', {req: req, res: res}); // Set up the transport context
|
||||
}
|
||||
next();
|
||||
currentDomain.run(function() {
|
||||
ns.run(function processRequestInContext(context) {
|
||||
// Run the code in the context of the namespace
|
||||
if (enableHttpContext) {
|
||||
ns.set('http', {req: req, res: res}); // Set up the transport context
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
var it = require('./util/it');
|
||||
var describe = require('./util/describe');
|
||||
var Domain = require('domain');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
describe('loopback', function() {
|
||||
var nameCounter = 0;
|
||||
|
@ -388,4 +391,72 @@ describe('loopback', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.onServer('loopback.getCurrentContext', function() {
|
||||
var runInOtherDomain;
|
||||
var runnerInterval;
|
||||
|
||||
before(function setupRunInOtherDomain() {
|
||||
var emitterInOtherDomain = new EventEmitter();
|
||||
Domain.create().add(emitterInOtherDomain);
|
||||
|
||||
runInOtherDomain = function(fn) {
|
||||
emitterInOtherDomain.once('run', fn);
|
||||
};
|
||||
|
||||
runnerInterval = setInterval(function() {
|
||||
emitterInOtherDomain.emit('run');
|
||||
}, 10);
|
||||
});
|
||||
|
||||
after(function tearDownRunInOtherDomain() {
|
||||
clearInterval(runnerInterval);
|
||||
});
|
||||
|
||||
// See the following two items for more details:
|
||||
// https://github.com/strongloop/loopback/issues/809
|
||||
// https://github.com/strongloop/loopback/pull/337#issuecomment-61680577
|
||||
it('preserves callback domain', function(done) {
|
||||
var app = loopback();
|
||||
app.use(loopback.rest());
|
||||
app.dataSource('db', { connector: 'memory' });
|
||||
|
||||
var TestModel = loopback.createModel({ name: 'TestModel' });
|
||||
app.model(TestModel, { dataSource: 'db', public: true });
|
||||
|
||||
// function for remote method
|
||||
TestModel.test = function(inst, cb) {
|
||||
var tmpCtx = loopback.getCurrentContext();
|
||||
if (tmpCtx) tmpCtx.set('data', 'a value stored in context');
|
||||
if (process.domain) cb = process.domain.bind(cb); // IMPORTANT
|
||||
runInOtherDomain(cb);
|
||||
};
|
||||
|
||||
// remote method
|
||||
TestModel.remoteMethod('test', {
|
||||
accepts: { arg: 'inst', type: uniqueModelName },
|
||||
returns: { root: true },
|
||||
http: { path: '/test', verb: 'get' }
|
||||
});
|
||||
|
||||
// after remote hook
|
||||
TestModel.afterRemote('**', function(ctxx, inst, next) {
|
||||
var tmpCtx = loopback.getCurrentContext();
|
||||
if (tmpCtx) {
|
||||
ctxx.result.data = tmpCtx.get('data');
|
||||
}else {
|
||||
ctxx.result.data = 'context not available';
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/TestModels/test')
|
||||
.end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
expect(res.body.data).to.equal('a value stored in context');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1164,8 +1164,15 @@ describe('relations - integration', function() {
|
|||
{ properties: { text: 'string' }, dataSource: 'db',
|
||||
plural: 'notes' }
|
||||
);
|
||||
var Chapter = app.model(
|
||||
'Chapter',
|
||||
{ properties: { name: 'string' }, dataSource: 'db',
|
||||
plural: 'chapters' }
|
||||
);
|
||||
Book.hasMany(Page);
|
||||
Book.hasMany(Chapter);
|
||||
Page.hasMany(Note);
|
||||
Chapter.hasMany(Note);
|
||||
Image.belongsTo(Book);
|
||||
|
||||
// fake a remote method that match the filter in Model.nestRemoting()
|
||||
|
@ -1176,6 +1183,7 @@ describe('relations - integration', function() {
|
|||
Page.remoteMethod('__throw__errors', { isStatic: false, http: { path: '/throws', verb: 'get' } });
|
||||
|
||||
Book.nestRemoting('pages');
|
||||
Book.nestRemoting('chapters');
|
||||
Image.nestRemoting('book');
|
||||
|
||||
expect(Book.prototype['__findById__pages__notes']).to.be.a.function;
|
||||
|
@ -1212,6 +1220,19 @@ describe('relations - integration', function() {
|
|||
});
|
||||
});
|
||||
|
||||
before(function createChapters(done) {
|
||||
var test = this;
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
before(function createCover(done) {
|
||||
var test = this;
|
||||
app.models.Image.create({ name: 'Cover 1', book: test.book },
|
||||
|
@ -1300,6 +1321,16 @@ describe('relations - integration', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('should nest remote hooks of ModelTo - hasMany findById', function(done) {
|
||||
var test = this;
|
||||
this.get('/api/books/' + test.book.id + '/chapters/' + test.chapter.id + '/notes/' + test.cnote.id)
|
||||
.expect(200, function(err, res) {
|
||||
expect(res.headers['x-before']).to.empty();
|
||||
expect(res.headers['x-after']).to.empty();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have proper http.path for remoting', function() {
|
||||
[app.models.Book, app.models.Image].forEach(function(Model) {
|
||||
Model.sharedClass.methods().forEach(function(method) {
|
||||
|
|
|
@ -11,7 +11,7 @@ describe('User', function() {
|
|||
var validCredentialsEmailVerified = {email: 'foo1@bar.com', password: 'bar1', emailVerified: true};
|
||||
var validCredentialsEmailVerifiedOverREST = {email: 'foo2@bar.com', password: 'bar2', emailVerified: true};
|
||||
var validCredentialsWithTTL = {email: 'foo@bar.com', password: 'bar', ttl: 3600};
|
||||
var invalidCredentials = {email: 'foo1@bar.com', password: 'bar1'};
|
||||
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
|
||||
var incompleteCredentials = {password: 'bar1'};
|
||||
|
||||
beforeEach(function() {
|
||||
|
@ -142,6 +142,9 @@ describe('User', function() {
|
|||
.expect(200)
|
||||
.send(validCredentialsEmailVerifiedOverREST)
|
||||
.end(function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert(!res.body.emailVerified);
|
||||
done();
|
||||
});
|
||||
|
@ -197,6 +200,14 @@ describe('User', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('Login should only allow correct credentials', function(done) {
|
||||
User.login(invalidCredentials, function(err, accessToken) {
|
||||
assert(err);
|
||||
assert(!accessToken);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Login a user over REST by providing credentials', function(done) {
|
||||
request(app)
|
||||
.post('/users/login')
|
||||
|
@ -204,7 +215,9 @@ 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);
|
||||
|
@ -223,6 +236,9 @@ describe('User', function() {
|
|||
.expect(401)
|
||||
.send(invalidCredentials)
|
||||
.end(function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -234,6 +250,9 @@ describe('User', function() {
|
|||
.expect(400)
|
||||
.send(incompleteCredentials)
|
||||
.end(function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -244,8 +263,11 @@ describe('User', function() {
|
|||
.set('Content-Type', null)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(400)
|
||||
.send(validCredentials)
|
||||
.send(JSON.stringify(validCredentials))
|
||||
.end(function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -257,7 +279,9 @@ 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')
|
||||
|
@ -273,7 +297,9 @@ 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')
|
||||
|
@ -282,15 +308,6 @@ describe('User', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('Login should only allow correct credentials', function(done) {
|
||||
User.create({email: 'foo22@bar.com', password: 'bar'}, function(user, err) {
|
||||
User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, accessToken) {
|
||||
assert(err);
|
||||
assert(!accessToken);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function assertGoodToken(accessToken) {
|
||||
|
@ -329,7 +346,9 @@ 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);
|
||||
|
@ -346,6 +365,9 @@ describe('User', function() {
|
|||
.expect(401)
|
||||
.send(validCredentials)
|
||||
.end(function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -535,7 +557,9 @@ 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);
|
||||
|
@ -558,7 +582,9 @@ 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');
|
||||
|
@ -647,7 +673,9 @@ 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -678,7 +706,9 @@ 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -761,7 +791,9 @@ describe('User', function() {
|
|||
+ '&redirect=' + encodeURIComponent(options.redirect))
|
||||
.expect(400)
|
||||
.end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert(res.body.error);
|
||||
done();
|
||||
});
|
||||
|
@ -772,9 +804,17 @@ describe('User', function() {
|
|||
|
||||
describe('Password Reset', function() {
|
||||
describe('User.resetPassword(options, cb)', function() {
|
||||
var email = 'foo@bar.com';
|
||||
|
||||
it('Requires email address to reset password', function(done) {
|
||||
User.resetPassword({ }, function(err) {
|
||||
assert(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Creates a temp accessToken to allow a user to change password', function(done) {
|
||||
var calledBack = false;
|
||||
var email = 'foo@bar.com';
|
||||
|
||||
User.resetPassword({
|
||||
email: email
|
||||
|
@ -794,6 +834,35 @@ describe('User', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Password reset over REST rejected without email address', function(done) {
|
||||
request(app)
|
||||
.post('/users/reset')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(400)
|
||||
.send({ })
|
||||
.end(function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Password reset over REST requires email address', function(done) {
|
||||
request(app)
|
||||
.post('/users/reset')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(204)
|
||||
.send({ email: email })
|
||||
.end(function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.deepEqual(res.body, { });
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue