loopback-datasource-juggler/test/async-observer.test.js

416 lines
12 KiB
JavaScript
Raw Normal View History

// Copyright IBM Corp. 2015,2019. All Rights Reserved.
2016-04-01 22:25:16 +00:00
// Node module: loopback-datasource-juggler
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
2019-05-08 15:45:37 +00:00
2016-08-22 19:55:22 +00:00
'use strict';
2016-04-01 22:25:16 +00:00
2018-12-07 14:54:29 +00:00
const ModelBuilder = require('../').ModelBuilder;
const should = require('./init');
describe('async observer', function() {
2018-12-07 14:54:29 +00:00
let TestModel;
beforeEach(function defineTestModel() {
2018-12-07 14:54:29 +00:00
const modelBuilder = new ModelBuilder();
2016-08-19 17:46:59 +00:00
TestModel = modelBuilder.define('TestModel', {name: String});
});
it('calls registered async observers', function(done) {
2018-12-07 14:54:29 +00:00
const notifications = [];
TestModel.observe('before', pushAndNext(notifications, 'before'));
TestModel.observe('after', pushAndNext(notifications, 'after'));
TestModel.notifyObserversOf('before', {}, function(err) {
if (err) return done(err);
notifications.push('call');
TestModel.notifyObserversOf('after', {}, function(err) {
if (err) return done(err);
notifications.should.eql(['before', 'call', 'after']);
done();
});
});
});
it('allows multiple observers for the same operation', function(done) {
2018-12-07 14:54:29 +00:00
const notifications = [];
TestModel.observe('event', pushAndNext(notifications, 'one'));
TestModel.observe('event', pushAndNext(notifications, 'two'));
TestModel.notifyObserversOf('event', {}, function(err) {
if (err) return done(err);
notifications.should.eql(['one', 'two']);
done();
});
});
2015-05-26 17:15:39 +00:00
it('allows multiple operations to be notified in one call', function(done) {
2018-12-07 14:54:29 +00:00
const notifications = [];
2015-05-26 17:15:39 +00:00
TestModel.observe('event1', pushAndNext(notifications, 'one'));
TestModel.observe('event2', pushAndNext(notifications, 'two'));
TestModel.notifyObserversOf(['event1', 'event2'], {}, function(err) {
if (err) return done(err);
notifications.should.eql(['one', 'two']);
done();
});
});
it('inherits observers from base model', function(done) {
2018-12-07 14:54:29 +00:00
const notifications = [];
TestModel.observe('event', pushAndNext(notifications, 'base'));
2018-12-07 14:54:29 +00:00
const Child = TestModel.extend('Child');
Child.observe('event', pushAndNext(notifications, 'child'));
Child.notifyObserversOf('event', {}, function(err) {
if (err) return done(err);
notifications.should.eql(['base', 'child']);
done();
});
});
2015-05-26 17:15:39 +00:00
it('allow multiple operations to be notified with base models', function(done) {
2018-12-07 14:54:29 +00:00
const notifications = [];
2015-05-26 17:15:39 +00:00
TestModel.observe('event1', pushAndNext(notifications, 'base1'));
TestModel.observe('event2', pushAndNext(notifications, 'base2'));
2018-12-07 14:54:29 +00:00
const Child = TestModel.extend('Child');
2015-05-26 17:15:39 +00:00
Child.observe('event1', pushAndNext(notifications, 'child1'));
Child.observe('event2', pushAndNext(notifications, 'child2'));
Child.notifyObserversOf(['event1', 'event2'], {}, function(err) {
if (err) return done(err);
notifications.should.eql(['base1', 'child1', 'base2', 'child2']);
done();
});
});
it('does not modify observers in the base model', function(done) {
2018-12-07 14:54:29 +00:00
const notifications = [];
TestModel.observe('event', pushAndNext(notifications, 'base'));
2018-12-07 14:54:29 +00:00
const Child = TestModel.extend('Child');
Child.observe('event', pushAndNext(notifications, 'child'));
TestModel.notifyObserversOf('event', {}, function(err) {
if (err) return done(err);
notifications.should.eql(['base']);
done();
});
});
it('always calls inherited observers', function(done) {
2018-12-07 14:54:29 +00:00
const notifications = [];
TestModel.observe('event', pushAndNext(notifications, 'base'));
2018-12-07 14:54:29 +00:00
const Child = TestModel.extend('Child');
// Important: there are no observers on the Child model
Child.notifyObserversOf('event', {}, function(err) {
if (err) return done(err);
notifications.should.eql(['base']);
done();
});
});
it('can remove observers', function(done) {
2018-12-07 14:54:29 +00:00
const notifications = [];
function call(ctx, next) {
notifications.push('call');
process.nextTick(next);
}
TestModel.observe('event', call);
TestModel.removeObserver('event', call);
TestModel.notifyObserversOf('event', {}, function(err) {
if (err) return done(err);
notifications.should.eql([]);
done();
});
});
it('can clear all observers', function(done) {
2018-12-07 14:54:29 +00:00
const notifications = [];
function call(ctx, next) {
notifications.push('call');
process.nextTick(next);
}
TestModel.observe('event', call);
TestModel.observe('event', call);
TestModel.observe('event', call);
TestModel.clearObservers('event');
TestModel.notifyObserversOf('event', {}, function(err) {
if (err) return done(err);
notifications.should.eql([]);
done();
});
});
it('handles no observers', function(done) {
TestModel.notifyObserversOf('no-observers', {}, function(err) {
// the test passes when no error was raised
done(err);
});
});
it('passes context to final callback', function(done) {
2018-12-07 14:54:29 +00:00
const context = {};
TestModel.notifyObserversOf('event', context, function(err, ctx) {
2016-04-01 11:48:17 +00:00
(ctx || 'null').should.equal(context);
done();
});
});
2015-05-20 22:02:44 +00:00
describe('notifyObserversAround', function() {
2018-12-07 14:54:29 +00:00
let notifications;
2015-05-20 22:02:44 +00:00
beforeEach(function() {
notifications = [];
TestModel.observe('before execute',
pushAndNext(notifications, 'before execute'));
TestModel.observe('after execute',
pushAndNext(notifications, 'after execute'));
});
it('should notify before/after observers', function(done) {
2018-12-07 14:54:29 +00:00
const context = {};
2015-05-20 22:02:44 +00:00
function work(done) {
process.nextTick(function() {
done(null, 1);
});
}
TestModel.notifyObserversAround('execute', context, work,
function(err, result) {
notifications.should.eql(['before execute', 'after execute']);
result.should.eql(1);
done();
});
});
it('should allow work with context', function(done) {
2018-12-07 14:54:29 +00:00
const context = {};
2015-05-20 22:02:44 +00:00
function work(context, done) {
process.nextTick(function() {
done(null, 1);
});
}
TestModel.notifyObserversAround('execute', context, work,
function(err, result) {
notifications.should.eql(['before execute', 'after execute']);
result.should.eql(1);
done();
});
});
it('should notify before/after observers with multiple results',
function(done) {
2018-12-07 14:54:29 +00:00
const context = {};
2015-05-20 22:02:44 +00:00
function work(done) {
process.nextTick(function() {
done(null, 1, 2);
});
}
TestModel.notifyObserversAround('execute', context, work,
function(err, r1, r2) {
r1.should.eql(1);
r2.should.eql(2);
notifications.should.eql(['before execute', 'after execute']);
done();
});
});
2015-05-26 17:15:39 +00:00
it('should allow observers to skip other ones',
function(done) {
TestModel.observe('before invoke',
function(context, next) {
notifications.push('before invoke');
context.end(null, 0);
});
TestModel.observe('after invoke',
pushAndNext(notifications, 'after invoke'));
2018-12-07 14:54:29 +00:00
const context = {};
2015-05-26 17:15:39 +00:00
function work(done) {
process.nextTick(function() {
done(null, 1, 2);
});
}
TestModel.notifyObserversAround('invoke', context, work,
function(err, r1) {
r1.should.eql(0);
notifications.should.eql(['before invoke']);
done();
});
});
it('should allow observers to tweak results',
function(done) {
TestModel.observe('after invoke',
function(context, next) {
notifications.push('after invoke');
context.results = [3];
next();
});
2018-12-07 14:54:29 +00:00
const context = {};
2015-05-26 17:15:39 +00:00
function work(done) {
process.nextTick(function() {
done(null, 1, 2);
});
}
TestModel.notifyObserversAround('invoke', context, work,
function(err, r1) {
r1.should.eql(3);
notifications.should.eql(['after invoke']);
done();
});
});
2015-05-20 22:02:44 +00:00
});
it('resolves promises returned by observers', function(done) {
TestModel.observe('event', function(ctx) {
return Promise.resolve('value-to-ignore');
});
TestModel.notifyObserversOf('event', {}, function(err, ctx) {
// the test times out when the promises are not supported
done();
});
});
it('handles rejected promise returned by an observer', function(done) {
2018-12-07 14:54:29 +00:00
const testError = new Error('expected test error');
TestModel.observe('event', function(ctx) {
return Promise.reject(testError);
});
TestModel.notifyObserversOf('event', {}, function(err, ctx) {
err.should.eql(testError);
done();
});
});
it('returns a promise when no callback is provided', function() {
2018-12-07 14:54:29 +00:00
const context = {value: 'a-test-context'};
const p = TestModel.notifyObserversOf('event', context);
(p !== undefined).should.be.true;
return p.then(function(result) {
result.should.eql(context);
});
});
it('returns a rejected promise when no callback is provided', function() {
2018-12-07 14:54:29 +00:00
const testError = new Error('expected test error');
TestModel.observe('event', function(ctx, next) { next(testError); });
2018-12-07 14:54:29 +00:00
const p = TestModel.notifyObserversOf('event', context);
return p.then(
function(result) {
throw new Error('The promise should have been rejected.');
},
function(err) {
err.should.eql(testError);
},
);
});
it('should call after operation hook on error', function(done) {
const context = {
req: {},
};
const operationError = new Error('The operation failed without result');
let callCount = 0;
function fail(context, done) {
process.nextTick(() => {
done(operationError);
});
}
TestModel.observe('after execute error', function(ctx, next) {
callCount++;
next();
});
TestModel.notifyObserversAround('execute', context, fail, (err, ctx) => {
callCount.should.eql(1);
err.message.should.eql(operationError.message);
ctx.error.message.should.eql(operationError.message);
done();
});
});
it('should call after operation hook on error while overwriting error', function(done) {
const context = {
req: {},
};
const operationError = new Error('The operation failed without result');
const overwriteError = new Error('Overwriting the original error');
let callCount = 0;
function fail(context, done) {
process.nextTick(() => {
done(operationError);
});
}
TestModel.observe('after execute error', function(ctx, next) {
callCount++;
next(overwriteError);
});
TestModel.notifyObserversAround('execute', context, fail, (err, ctx) => {
callCount.should.eql(1);
err.message.should.eql(overwriteError.message);
ctx.error.message.should.eql(operationError.message);
done();
});
});
it('should call after operation hook on error while allowing to change err', function(done) {
const context = {
req: {},
};
const operationError = new Error('The operation failed without result');
let callCount = 0;
function fail(context, done) {
process.nextTick(() => {
done(operationError);
});
}
TestModel.observe('after execute error', function(ctx, next) {
callCount++;
const err = ctx.error;
next(err, ctx);
});
TestModel.notifyObserversAround('execute', context, fail, (err, ctx) => {
callCount.should.eql(1);
err.message.should.eql(operationError.message);
ctx.error.message.should.eql(operationError.message);
done();
});
});
});
function pushAndNext(array, value) {
return function(ctx, next) {
array.push(value);
process.nextTick(next);
};
}