starting unit tests

This commit is contained in:
ebarault 2017-03-23 10:18:28 +01:00
parent 844147517e
commit a657e8aa46
3 changed files with 161 additions and 69 deletions

View File

@ -399,26 +399,25 @@ app.enableAuth = function(options) {
}
};
this._verifyAuthModelRelations();
return this._verifyAuthModelRelations(app._warnOnBadAuthModelRelations)
.then(() => {
this.isAuthEnabled = true;
});
};
app._verifyAuthModelRelations = function() {
app._verifyAuthModelRelations = function(warnFn) {
let self = this;
// Allow unit-tests (but also LoopBack users) to disable the warnings
const warnOnBadSetup = self.get('_disableAuthModelRelationsWarnings') !== true;
const warnOnBadSetup = this.get('_warnOnBadAuthModelsSetup') !== false;
// Prevent the app from being killed although the configuration is inconsistent
const dismissRejection = self.get('_dismissBadUserConfigRejection') !== true;
const abortOnBadUserSetup = this.get('_abortOnBadUserSetup') !== false;
const AccessToken = self.registry.findModel('AccessToken');
const User = self.registry.findModel('User');
const models = self.models();
const AccessToken = this.registry.findModel('AccessToken');
const User = this.registry.findModel('User');
const models = this.models();
verifyUserModelsSetup().then(()=> {
verifyRelationsSetup();
});
return verifyUserModelsSetup().then(verifyRelationsSetup);
function verifyUserModelsSetup() {
let userModels = models.filter(model => {
@ -428,25 +427,22 @@ app._verifyAuthModelRelations = function() {
// we use Promise.reflect() here to let all the tests go without throwing,
// this way we can get all the logs for all models we are checking
return Promise.map(userModels, (model) => {
return scheduleVerification(model, hasMultipleUserModelsConfig).reflect();
})
.then((inspections) => {
return Promise.map(userModels,
model => scheduleVerification(model, hasMultipleUserModelsConfig).reflect()
)
.then(inspections => {
// proceed to next checkups if no error
let hasErrors = inspections.filter(inspection => {
return inspection.isRejected();
});
let hasErrors = inspections.filter(inspection => inspection.isRejected());
if (!hasErrors.length) {
return;
}
// else log and eventually kill the app
console.warn('Application setup is inconsistent: some models are set to use ' +
'the Multiple user models configuration while some other models are set to ' +
'use the Single user model configuration. The model config is listed below.');
warnFn('BAD_USER_MODELS_SETUP');
// detailed logs
inspections.forEach(inspection => {
console.log(inspection);
let modelSetup = (inspection.value() || inspection.reason()).modelSetup;
console.warn(
'Model %j of type %j is set to use the %j user model config',
@ -456,11 +452,14 @@ app._verifyAuthModelRelations = function() {
);
});
delete self.hasMultipleUserModelsConfig;
delete this.hasMultipleUserModelsConfig;
if (!dismissRejection) {
throw new Error('Application setup is inconsistent, please see the log for more ' +
'information');
if (abortOnBadUserSetup) {
const msg = 'Application setup is inconsistent, please see the log ' +
'for more information';
const error = new Error(msg);
error.code = 'BAD_USER_MODELS_SETUP';
throw error;
}
});
}
@ -501,7 +500,8 @@ app._verifyAuthModelRelations = function() {
if (model === User || model.prototype instanceof User) {
isInstanceOfUser = true;
const hasManyTokens = model.relations && model.relations.accessTokens;
hasMultipleUserModelsConfig = !!hasManyTokens.polymorphic;
// TODO: handle when hasManyTokens is undefined?
hasMultipleUserModelsConfig = !!(hasManyTokens.polymorphic);
} else if (model === AccessToken || model.prototype instanceof AccessToken) {
const belongsToUser = model.relations && model.relations.user;
// the test on belongsToUser is required as we allow AccessToken model not
@ -512,7 +512,7 @@ app._verifyAuthModelRelations = function() {
let modelSetup = {
model: model.modelName,
instanceOf: isInstanceOfUser ? 'User' : 'AccesToken',
instanceOf: isInstanceOfUser ? 'User' : 'AccessToken',
hasMultipleUserModelsConfig,
};
@ -531,30 +531,18 @@ app._verifyAuthModelRelations = function() {
function verifyAccessTokenRelations(Model) {
if (!warnOnBadSetup) return;
const belongsToUser = Model.relations && Model.relations.user;
if (belongsToUser) return;
const modelFrom = Model.modelName;
const relationsConfig = Model.settings.relations || {};
const userName = (relationsConfig.user || {}).model;
if (userName) {
console.warn(
'The model %j configures "belongsTo User-like models" relation ' +
'with target model %j. However, the model %j is not attached to ' +
'the application and therefore cannot be used by this relation. ' +
'This typically happens when the application has a custom ' +
'custom User subclass, but does not fix AccessToken relations ' +
'to use this new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, userName, userName);
const modelTo = (relationsConfig.user || {}).model;
if (modelTo) {
warnFn('CUSTOM_USER_MODEL_NOT_AVAILABLE', {modelFrom, modelTo});
return;
}
console.warn(
'The model %j does not have "belongsTo User-like model" relation ' +
'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName);
warnFn('MISSING_RELATION_TO_USER', {modelFrom});
}
function verifyUserRelations(Model) {
@ -563,10 +551,49 @@ app._verifyAuthModelRelations = function() {
const hasManyTokens = Model.relations && Model.relations.accessTokens;
if (hasManyTokens) return;
const modelFrom = Model.modelName;
const relationsConfig = Model.settings.relations || {};
const accessTokenName = (relationsConfig.accessTokens || {}).model;
if (accessTokenName) {
console.warn(
const modelTo = (relationsConfig.accessTokens || {}).model;
if (modelTo) {
warnFn('CUSTOM_ACCESS_TOKEN_MODEL_NOT_AVAILABLE', {modelFrom, modelTo});
return;
}
warnFn('MISSING_RELATION_TO_ACCESS_TOKEN', {modelFrom});
}
};
// function warnOnBadAuthModelRelations(code, args) {
app._warnOnBadAuthModelRelations = function(code, args) {
switch (code) {
case 'BAD_USER_MODELS_SETUP':
g.warn('Application setup is inconsistent: some models are set to use ' +
'the Multiple user models configuration while some other models are set to ' +
'use the Single user model configuration. The model config is listed below.');
break;
case 'CUSTOM_USER_MODEL_NOT_AVAILABLE':
g.warn(
'The model %j configures "belongsTo User-like models" relation ' +
'with target model %j. However, the model %j is not attached to ' +
'the application and therefore cannot be used by this relation. ' +
'This typically happens when the application has a custom ' +
'custom User subclass, but does not fix AccessToken relations ' +
'to use this new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
args.modelFrom, args.modelTo, args.modelTo);
break;
case 'MISSING_RELATION_TO_USER':
g.warn(
'The model %j does not have "belongsTo User-like model" relation ' +
'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
args.modelFrom);
break;
case 'CUSTOM_ACCESS_TOKEN_MODEL_NOT_AVAILABLE':
g.warn(
'The model %j configures "hasMany AccessToken-like models" relation ' +
'with target model %j. However, the model %j is not attached to ' +
'the application and therefore cannot be used by this relation. ' +
@ -574,15 +601,15 @@ app._verifyAuthModelRelations = function() {
'AccessToken subclass, but does not fix User relations to use this ' +
'new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, accessTokenName, accessTokenName);
return;
}
args.modelFrom, args.modelTo, args.modelTo);
break;
console.warn(
case 'MISSING_RELATION_TO_ACCESS_TOKEN':
g.warn(
'The model %j does not have "hasMany AccessToken-like models" relation ' +
'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName);
args.modelFrom);
}
};

View File

@ -18,6 +18,8 @@ var expect = require('./helpers/expect');
var it = require('./util/it');
var request = require('supertest');
var Promise = require('bluebird');
describe('app', function() {
var app;
beforeEach(function() {
@ -859,11 +861,12 @@ describe('app', function() {
});
describe.onServer('enableAuth', function() {
it('should set app.isAuthEnabled to true', function() {
it('sets app.isAuthEnabled to true', function() {
expect(app.isAuthEnabled).to.not.equal(true);
app.enableAuth();
return app.enableAuth().then(() => {
expect(app.isAuthEnabled).to.equal(true);
});
});
it('auto-configures required models to provided dataSource', function() {
var AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];
@ -871,8 +874,7 @@ describe('app', function() {
require('../lib/builtin-models')(app.registry);
var db = app.dataSource('db', {connector: 'memory'});
app.enableAuth({dataSource: 'db'});
return app.enableAuth({dataSource: 'db'}).then(() => {
expect(Object.keys(app.models)).to.include.members(AUTH_MODELS);
AUTH_MODELS.forEach(function(m) {
@ -881,6 +883,7 @@ describe('app', function() {
expect(Model.shared, m + '.shared').to.equal(m === 'User');
});
});
});
it('detects already configured subclass of a required model', function() {
var app = loopback({localRegistry: true, loadBuiltinModels: true});
@ -892,12 +895,74 @@ describe('app', function() {
const AccessToken = app.registry.getModel('AccessToken');
AccessToken.settings.relations.user.model = 'Customer';
app.enableAuth({dataSource: 'db'});
return app.enableAuth({dataSource: 'db'}).then(() => {
expect(Object.keys(app.models)).to.not.include('User');
});
});
describe('auth models config health check', function() {
var app, warnings;
beforeEach(function() {
app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'});
warnings = [];
function warnFn(code, args) {
warnings.push(Object.assign(args || {}, {code}));
}
app._warnOnBadAuthModelRelations = warnFn;
});
it('sets app.hasMultipleUserModelsConfig to false if the app ' +
'has a single User model correctly configured', function() {
return app.enableAuth({dataSource: 'db'}).then(() => {
expect(app.hasMultipleUserModelsConfig).to.be.false();
});
});
it('sets app.hasMultipleUserModelsConfig to true if the app ' +
'has multiple User models correctly configured', function() {
var Merchant = createModel(app, 'Merchant', {base: 'User'});
var Customer = createModel(app, 'Customer', {base: 'User'});
var Token = createModel(app, 'Token', {base: 'AccessToken'});
// Update AccessToken and Users to bind them through polymorphic relations
Token.belongsTo('user', {idName: 'id', polymorphic: {idType: 'string',
foreignKey: 'userId', discriminator: 'principalType'}});
Merchant.hasMany('accessTokens', {model: Token, polymorphic: {foreignKey: 'userId',
discriminator: 'principalType'}});
Customer.hasMany('accessTokens', {model: Token, polymorphic: {foreignKey: 'userId',
discriminator: 'principalType'}});
return app.enableAuth({dataSource: 'db'}).then(() => {
expect(app.hasMultipleUserModelsConfig).to.be.true();
});
});
it('warns if a custom user model is referenced in the ' +
'AccessToken "user" relation, but is not available', function() {
// Set AccessToken's belongsTo relation "user" to use a custom user model
// This model is deliberately not available
const AccessToken = app.registry.getModel('AccessToken');
AccessToken.settings.relations.user.model = 'Customer';
return app.enableAuth({dataSource: 'db'}).then(() => {
expect(warnings).to.eql([{
code: 'CUSTOM_USER_MODEL_NOT_AVAILABLE',
modelFrom: 'AccessToken',
modelTo: 'Customer',
}]);
});
});
// helpers
function createModel(app, name, options) {
var model = app.registry.createModel(Object.assign({name: name}, options));
app.model(model, {dataSource: 'db'});
return model;
}
});
});
describe.onServer('app.get(\'/\', loopback.status())', function() {
it('should return the status of the application', function(done) {
var app = loopback();

View File

@ -2442,7 +2442,7 @@ describe('User', function() {
it('handles subclassed user with no accessToken relation', () => {
// setup a new LoopBack app, we don't want to use shared models
app = loopback({localRegistry: true, loadBuiltinModels: true});
app.set('_disableAuthModelRelationsWarnings', true);
app.set('_warnOnBadAuthModelsSetup', false);
app.set('remoting', {errorHandler: {debug: true, log: false}});
app.dataSource('db', {connector: 'memory'});
const User = app.registry.createModel({