Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions common/models/role.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,10 @@ module.exports = function(Role) {
});

function isUserClass(modelClass) {
if (modelClass) {
return modelClass === loopback.User ||
modelClass.prototype instanceof loopback.User;
} else {
return false;
}
if (!modelClass) return false;
var User = modelClass.modelBuilder.models.User;
if (!User) return false;
return modelClass == User || modelClass.prototype instanceof User;
}

/*!
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,10 @@
"depd": "loopback-datasource-juggler/lib/browser.depd.js",
"bcrypt": false
},
"config": {
"ci": {
"debug": "*,-mocha:*,-eslint:*"
}
},
"license": "MIT"
}
78 changes: 37 additions & 41 deletions test/rest.middleware.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
var path = require('path');

describe('loopback.rest', function() {
var MyModel;
var app, MyModel;

beforeEach(function() {
var ds = app.dataSource('db', { connector: loopback.Memory });
MyModel = ds.createModel('MyModel', {name: String});
loopback.autoAttach();
// override the global app object provided by test/support.js
// and create a local one that does not share state with other tests
app = loopback({ localRegistry: true, loadBuiltinModels: true });
var db = app.dataSource('db', { connector: 'memory' });
MyModel = app.registry.createModel('MyModel');
MyModel.attachTo(db);
});

it('works out-of-the-box', function(done) {
Expand Down Expand Up @@ -101,7 +105,7 @@ describe('loopback.rest', function() {
});

it('should honour `remoting.rest.supportedTypes`', function(done) {
var app = loopback();
var app = loopback({ localRegistry: true });

// NOTE it is crucial to set `remoting` before creating any models
var supportedTypes = ['json', 'application/javascript', 'text/javascript'];
Expand All @@ -117,39 +121,37 @@ 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);
});

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)
Expand Down Expand Up @@ -268,25 +270,25 @@ describe('loopback.rest', function() {

it('should enable context using loopback.context', function(done) {
app.use(loopback.context({ enableHttpContext: true }));
app.enableAuth();
app.enableAuth({ dataSource: 'db' });
app.use(loopback.rest());

invokeGetToken(done);
});

it('should enable context with loopback.rest', function(done) {
app.enableAuth();
app.set('remoting', { context: { enableHttpContext: true } });
app.enableAuth({ dataSource: 'db' });
app.set('remoting', { context: { enableHttpContext: true }});
app.use(loopback.rest());

invokeGetToken(done);
});

it('should support explicit context', function(done) {
app.enableAuth();
app.enableAuth({ dataSource: 'db' });
app.use(loopback.context());
app.use(loopback.token(
{ model: loopback.getModelByType(loopback.AccessToken) }));
{ model: app.registry.getModelByType('AccessToken') }));
app.use(function(req, res, next) {
loopback.getCurrentContext().set('accessToken', req.accessToken);
next();
Expand Down Expand Up @@ -321,32 +323,26 @@ describe('loopback.rest', function() {
});

function givenUserModelWithAuth() {
// NOTE(bajtos) It is important to create a custom AccessToken model here,
// in order to overwrite the entry created by previous tests in
// the global model registry
app.model('accessToken', {
options: {
base: 'AccessToken'
},
dataSource: 'db'
});
return app.model('user', {
options: {
base: 'User',
relations: {
accessTokens: {
model: 'accessToken',
type: 'hasMany',
foreignKey: 'userId'
}
}
},
dataSource: 'db'
});
var AccessToken = app.registry.getModel('AccessToken');
app.model(AccessToken, { dataSource: 'db' });
var User = app.registry.getModel('User');
app.model(User, { dataSource: 'db' });

// NOTE(bajtos) This is puzzling to me. The built-in User & AccessToken
// models should come with both relations already set up, i.e. the
// following two lines should not be neccessary.
// And it does behave that way when only tests in this file are run.
// However, when I run the full test suite (all files), the relations
// get broken.
AccessToken.belongsTo(User, { as: 'user', foreignKey: 'userId' });
User.hasMany(AccessToken, { as: 'accessTokens', foreignKey: 'userId' });

return User;
}

function givenLoggedInUser(cb, done) {
var credentials = { email: 'user@example.com', password: 'pwd' };
var User = app.models.user;
var User = app.models.User;
User.create(credentials,
function(err, user) {
if (err) return done(err);
Expand Down
23 changes: 23 additions & 0 deletions test/role.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,4 +407,27 @@ describe('role model', function() {
});
});

describe('isOwner', function() {
it('supports app-local model registry', function(done) {
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
app.dataSource('db', { connector: 'memory' });
// attach all auth-related models to 'db' datasource
app.enableAuth({ dataSource: 'db' });

var Role = app.models.Role;
var User = app.models.User;

var u = app.registry.findModel('User');
var credentials = { email: 'test@example.com', password: 'pass' };
User.create(credentials, function(err, user) {
if (err) return done(err);

Role.isOwner(User, user.id, user.id, function(err, result) {
if (err) return done(err);
expect(result, 'isOwner result').to.equal(true);
done();
});
});
});
});
});
98 changes: 52 additions & 46 deletions test/user.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
require('./support');
var loopback = require('../');
var User;
var AccessToken;
var MailConnector = require('../lib/connectors/mail');

var userMemory = loopback.createDataSource({
connector: 'memory'
});
var User, AccessToken;

describe('User', function() {
var validCredentialsEmail = 'foo@bar.com';
Expand All @@ -19,45 +13,59 @@ describe('User', function() {
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
var incompleteCredentials = {password: 'bar1'};

var defaultApp;
// Create a local app variable to prevent clashes with the global
// variable shared by all tests. While this should not be necessary if
// the tests were written correctly, it turns out that's not the case :(
var app;

beforeEach(function setupAppAndModels(done) {
// override the global app object provided by test/support.js
// and create a local one that does not share state with other tests
app = loopback({ localRegistry: true, loadBuiltinModels: true });
app.dataSource('db', { connector: 'memory' });

// setup Email model, it's needed by User tests
app.dataSource('email', {
connector: loopback.Mail,
transports: [{ type: 'STUB' }],
});
var Email = app.registry.getModel('Email');
app.model(Email, { dataSource: 'email' });

// attach User and related models
User = app.registry.createModel('TestUser', {}, {
base: 'User',
http: { path: 'test-users' },
});
app.model(User, { dataSource: 'db' });

beforeEach(function() {
// FIXME: [rfeng] Remove loopback.User.app so that remote hooks don't go
// to the wrong app instance
defaultApp = loopback.User.app;
loopback.User.app = null;
User = loopback.User.extend('TestUser', {}, {http: {path: 'test-users'}});
AccessToken = loopback.AccessToken.extend('TestAccessToken');
User.email = loopback.Email.extend('email');
loopback.autoAttach();
AccessToken = app.registry.getModel('AccessToken');
app.model(AccessToken, { dataSource: 'db' });

User.email = Email;

// Update the AccessToken relation to use the subclass of User
AccessToken.belongsTo(User, {as: 'user', foreignKey: 'userId'});
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});

// Speed up the password hashing algorithm
// for tests using the built-in User model
User.settings.saltWorkFactor = 4;

// allow many User.afterRemote's to be called
User.setMaxListeners(0);

});

beforeEach(function(done) {
app.enableAuth();
app.use(loopback.token({model: AccessToken}));
app.enableAuth({ dataSource: 'db' });
app.use(loopback.token({ model: AccessToken }));
app.use(loopback.rest());
app.model(User);

User.create(validCredentials, function(err, user) {
if (err) return done(err);
User.create(validCredentialsEmailVerified, done);
});
});

afterEach(function(done) {
loopback.User.app = defaultApp;
User.destroyAll(function(err) {
User.accessToken.destroyAll(done);
});
});

describe('User.create', function() {
it('Create a new user', function(done) {
User.create({email: 'f@b.com', password: 'bar'}, function(err, user) {
Expand Down Expand Up @@ -694,12 +702,19 @@ describe('User', function() {
var User;
var AccessToken;

before(function() {
User = loopback.User.extend('RealmUser', {},
{realmRequired: true, realmDelimiter: ':'});
AccessToken = loopback.AccessToken.extend('RealmAccessToken');
beforeEach(function() {
User = app.registry.createModel('RealmUser', {}, {
base: 'TestUser',
realmRequired: true,
realmDelimiter: ':',
});

loopback.autoAttach();
AccessToken = app.registry.createModel('RealmAccessToken', {}, {
base: 'AccessToken',
});

app.model(AccessToken, { dataSource: 'db' });
app.model(User, { dataSource: 'db' });

// Update the AccessToken relation to use the subclass of User
AccessToken.belongsTo(User, {as: 'user', foreignKey: 'userId'});
Expand Down Expand Up @@ -770,15 +785,6 @@ describe('User', function() {
});
});

afterEach(function(done) {
User.deleteAll({realm: 'realm1'}, function(err) {
if (err) {
return done(err);
}
User.deleteAll({realm: 'realm2'}, done);
});
});

it('rejects a user by without realm', function(done) {
User.login(credentialWithoutRealm, function(err, accessToken) {
assert(err);
Expand Down Expand Up @@ -828,11 +834,11 @@ describe('User', function() {
});

describe('User.login with realmRequired but no realmDelimiter', function() {
before(function() {
beforeEach(function() {
User.settings.realmDelimiter = undefined;
});

after(function() {
afterEach(function() {
User.settings.realmDelimiter = ':';
});

Expand Down Expand Up @@ -1534,7 +1540,7 @@ describe('User', function() {
describe('ctor', function() {
it('exports default Email model', function() {
expect(User.email, 'User.email').to.be.a('function');
expect(User.email.modelName, 'modelName').to.eql('email');
expect(User.email.modelName, 'modelName').to.eql('Email');
});

it('exports default AccessToken model', function() {
Expand Down