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
31 changes: 16 additions & 15 deletions lib/plugins/boot-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,24 @@ function runScripts(app, list, callback) {

async.eachSeries(functions, function(f, done) {
debug('Running script %s', f.path);
if (f.func.length >= 2) {
debug('Starting async function %s', f.path);
f.func(app, function(err) {
debug('Async function finished %s', f.path);
done(err);
});
} else {
debug('Starting sync function %s', f.path);
var error;
try {
f.func(app);
var cb = function(err) {
debug('Async function %s %s', err ? 'failed' : 'finished', f.path);
done(err);
// Make sure done() isn't called twice, e.g. if a script returns a
// thenable object and also calls the passed callback.
cb = function() {};
};
try {
var result = f.func(app, cb);
if (result && typeof result.then === 'function') {
result.then(function() { cb(); }, cb);
} else if (f.func.length < 2) {
debug('Sync function finished %s', f.path);
} catch (err) {
debug('Sync function failed %s', f.path, err);
error = err;
done();
}
done(error);
} catch (err) {
debug('Sync function failed %s', f.path, err);
done(err);
}
}, callback);
}
148 changes: 113 additions & 35 deletions test/executor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,15 @@ describe('executor', function() {
'barLoaded',
'barSyncLoaded',
'fooLoaded',
'promiseLoaded',
'thenableLoaded',
'barStarted',
'barFinished',
'barSyncExecuted',
'promiseStarted',
'promiseFinished',
'thenableStarted',
'thenableFinished',
]);
});
});
Expand All @@ -322,60 +328,132 @@ describe('executor', function() {
'barLoaded',
'barSyncLoaded',
'fooLoaded',
'promiseLoaded',
'thenableLoaded',
'barStarted',
'barFinished',
'barSyncExecuted',
'promiseStarted',
'promiseFinished',
'thenableStarted',
'thenableFinished',
]);
done();
});
});
});
});

describe('for mixins', function() {
var options;
beforeEach(function() {
appdir.writeFileSync('custom-mixins/example.js',
'module.exports = ' +
'function(Model, options) {}');
describe('with boot script returning a rejected promise', function() {
before(function() {
// Tell simple-app/boot/reject.js to return a rejected promise
process.rejectPromise = true;
});

appdir.writeFileSync('custom-mixins/time-stamps.js',
'module.exports = ' +
'function(Model, options) {}');
after(function() {
delete process.rejectPromise;
});

appdir.writeConfigFileSync('custom-mixins/time-stamps.json', {
name: 'Timestamping',
it('receives rejected promise as callback error',
function(done) {
simpleAppInstructions(function(err, context) {
if (err) return done(err);
boot.execute(app, context.instructions, function(err) {
expect(err).to.exist.and.be.an.instanceOf(Error)
.with.property('message', 'reject');
done();
});

options = {
appRootDir: appdir.PATH,
};
});
});
});

it('defines mixins from instructions - using `mixinDirs`',
function(done) {
options.mixinDirs = ['./custom-mixins'];
boot(app, options, function(err) {
if (err) return done(err);
var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
done();
});
describe('with boot script throwing an error', function() {
before(function() {
// Tell simple-app/boot/throw.js to throw an error
process.throwError = true;
});

after(function() {
delete process.throwError;
});

it('receives thrown error as callback errors',
function(done) {
simpleAppInstructions(function(err, context) {
if (err) return done(err);
boot.execute(app, context.instructions, function(err) {
expect(err).to.exist.and.be.an.instanceOf(Error)
.with.property('message', 'throw');
done();
});
});
});
});

it('defines mixins from instructions - using `mixinSources`',
function(done) {
options.mixinSources = ['./custom-mixins'];
boot(app, options, function(err) {
if (err) return done(err);
describe('with boot script returning a promise and calling callback',
function() {
before(function() {
process.promiseAndCallback = true;
});

var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
done();
});
after(function() {
delete process.promiseAndCallback;
});

it('should only call the callback once', function(done) {
simpleAppInstructions(function(err, context) {
if (err) return done(err);
// Note: Mocha will fail this test if done() is called twice
boot.execute(app, context.instructions, done);
});
});
}
);

describe('for mixins', function() {
var options;
beforeEach(function() {
appdir.writeFileSync('custom-mixins/example.js',
'module.exports = ' +
'function(Model, options) {}');

appdir.writeFileSync('custom-mixins/time-stamps.js',
'module.exports = ' +
'function(Model, options) {}');

appdir.writeConfigFileSync('custom-mixins/time-stamps.json', {
name: 'Timestamping',
});

options = {
appRootDir: appdir.PATH,
};
});

it('defines mixins from instructions - using `mixinDirs`',
function(done) {
options.mixinDirs = ['./custom-mixins'];
boot(app, options, function(err) {
if (err) return done(err);
var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
done();
});
});

it('defines mixins from instructions - using `mixinSources`',
function(done) {
options.mixinSources = ['./custom-mixins'];
boot(app, options, function(err) {
if (err) return done(err);

var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['Example', 'Timestamping']);
done();
});
});
});

describe('with PaaS and npm env variables', function() {
Expand Down
15 changes: 15 additions & 0 deletions test/fixtures/simple-app/boot/promise-callback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

var Promise = require('bluebird');

module.exports = function(app, callback) {
callback();
if (process.promiseAndCallback) {
return Promise.reject();
}
};
21 changes: 21 additions & 0 deletions test/fixtures/simple-app/boot/promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

var Promise = require('bluebird');

process.bootFlags.push('promiseLoaded');
module.exports = function(app) {
process.bootFlags.push('promiseStarted');
return Promise.resolve({
then: function(onFulfill, onReject) {
process.nextTick(function() {
process.bootFlags.push('promiseFinished');
onFulfill();
});
},
});
};
14 changes: 14 additions & 0 deletions test/fixtures/simple-app/boot/reject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

var Promise = require('bluebird');

module.exports = function(app) {
if (process.rejectPromise) {
return Promise.reject(new Error('reject'));
}
};
19 changes: 19 additions & 0 deletions test/fixtures/simple-app/boot/thenable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

process.bootFlags.push('thenableLoaded');
module.exports = function(app) {
process.bootFlags.push('thenableStarted');
return {
then: function(onFulfill, onReject) {
process.nextTick(function() {
process.bootFlags.push('thenableFinished');
onFulfill();
});
},
};
};
12 changes: 12 additions & 0 deletions test/fixtures/simple-app/boot/throw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

module.exports = function(app) {
if (process.throwError) {
throw new Error('throw');
}
};