From 09ad9e4711dab7119e7ed038c698932e0ff047f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 12 Apr 2016 13:42:57 +0200 Subject: [PATCH 1/3] test: extract context-test-helpers --- test/helpers/context-test-helpers.js | 71 ++++ test/persistence-hooks.suite.js | 576 +++++++++++++-------------- 2 files changed, 337 insertions(+), 310 deletions(-) create mode 100644 test/helpers/context-test-helpers.js diff --git a/test/helpers/context-test-helpers.js b/test/helpers/context-test-helpers.js new file mode 100644 index 00000000..cb8d0dc4 --- /dev/null +++ b/test/helpers/context-test-helpers.js @@ -0,0 +1,71 @@ +// Copyright IBM Corp. 2016. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +var traverse = require('traverse'); + +exports.ContextRecorder = ContextRecorder; +exports.deepCloneToObject = deepCloneToObject; +exports.aCtxForModel = aCtxForModel; + +function ContextRecorder(initialValue) { + if (!(this instanceof ContextRecorder)) { + return new ContextRecorder(initialValue); + } + this.records = initialValue; +}; + +ContextRecorder.prototype.recordAndNext = function(transformFm) { + var self = this; + return function(context, next) { + if (typeof transformFm === 'function') { + transformFm(context); + } + + context = deepCloneToObject(context); + context.hookState.test = true; + + if (typeof self.records === 'string') { + self.records = context; + return next(); + } + + if (!Array.isArray(self.records)) { + self.records = [self.records]; + } + + self.records.push(context); + next(); + }; +}; + +function deepCloneToObject(obj) { + return traverse(obj).map(function(x) { + if (x === undefined) { + // RDBMSs return null + return null; + } + if (x && x.toObject) + return x.toObject(true); + if (x && typeof x === 'function' && x.modelName) + return '[ModelCtor ' + x.modelName + ']'; + }); +} + +function aCtxForModel(TestModel, ctx) { + ctx.Model = TestModel; + + if (!ctx.hookState) { + ctx.hookState = {}; + } + + if (!('test' in ctx.hookState)) { + ctx.hookState.test = true; + } + + if (!ctx.options) { + ctx.options = {}; + } + return deepCloneToObject(ctx); +} diff --git a/test/persistence-hooks.suite.js b/test/persistence-hooks.suite.js index d6e0ca95..ebcc06d8 100644 --- a/test/persistence-hooks.suite.js +++ b/test/persistence-hooks.suite.js @@ -4,7 +4,11 @@ // License text available at https://opensource.org/licenses/MIT var ValidationError = require('../').ValidationError; -var traverse = require('traverse'); + +var contextTestHelpers = require('./helpers/context-test-helpers'); +var ContextRecorder = contextTestHelpers.ContextRecorder; +var deepCloneToObject = contextTestHelpers.deepCloneToObject; +var aCtxForModel = contextTestHelpers.aCtxForModel; module.exports = function(dataSource, should, connectorCapabilities) { if (!connectorCapabilities) connectorCapabilities = {}; @@ -12,7 +16,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { console.warn('The connector does not support a recently added feature: replaceOrCreateReportsNewInstance'); } describe('Persistence hooks', function() { - var observedContexts, expectedError, observersCalled; + var ctxRecorder, expectedError, observersCalled; var TestModel, existingInstance; var migrated = false, lastId; var triggered; @@ -20,7 +24,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { var undefinedValue = undefined; beforeEach(function setupDatabase(done) { - observedContexts = "hook not called"; + ctxRecorder = new ContextRecorder('hook not called'); expectedError = new Error('test error'); observersCalled = []; @@ -78,12 +82,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `access` hook', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.find({ where: { id: '1' } }, function(err, list) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - query: { where: { id: '1' } } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + query: { where: { id: '1' }}, })); done(); }); @@ -112,12 +116,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `access` hook for geo queries', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.find({ where: { geo: { near: '10,20' }}}, function(err, list) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - query: { where: { geo: { near: '10,20' } } } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + query: { where: { geo: { near: '10,20' }}}, })); done(); }); @@ -137,7 +141,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies updates from `loaded` hook', function(done) { - TestModel.observe('loaded', pushContextAndNext(function(ctx) { + TestModel.observe('loaded', ctxRecorder.recordAndNext(function(ctx) { ctx.instance.extra = 'hook data'; })); @@ -146,15 +150,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { function (err, list) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { - id: "1", - name: "first", - extra: "hook data" + id: '1', + name: 'first', + extra: 'hook data', }, isNewInstance: false, - hookState: { test: true }, - options: {} + options: {}, })); done(); }); @@ -191,11 +194,11 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before save` hook', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); TestModel.create({ name: 'created' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: instance.id, name: 'created', @@ -231,22 +234,22 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('sends `before save` for each model in an array', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); TestModel.create( [{ name: '1' }, { name: '2' }], function(err, list) { if (err) return done(err); // Creation of multiple instances is executed in parallel - observedContexts.sort(function(c1, c2) { + ctxRecorder.records.sort(function(c1, c2) { return c1.instance.name - c2.instance.name; }); - observedContexts.should.eql([ - aTestModelCtx({ + ctxRecorder.records.should.eql([ + aCtxForModel(TestModel, { instance: { id: list[0].id, name: '1', extra: undefined }, isNewInstance: true }), - aTestModelCtx({ + aCtxForModel(TestModel, { instance: { id: list[1].id, name: '2', extra: undefined }, isNewInstance: true }), @@ -266,14 +269,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); TestModel.create( { id: 'new-id', name: 'a name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: 'new-id', name: 'a name' }, isNewInstance: true, currentInstance: { extra: null, id: 'new-id', name: 'a name' } @@ -284,7 +287,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies updates from `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext(function(ctx){ + TestModel.observe('persist', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -317,7 +320,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `loaded` hook', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); // By default, the instance passed to create callback is NOT updated // with the changes made through persist/loaded hooks. To preserve @@ -329,7 +332,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: 'new-id', name: 'a name' }, isNewInstance: true })); @@ -349,7 +352,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies updates from `loaded` hook', function(done) { - TestModel.observe('loaded', pushContextAndNext(function(ctx){ + TestModel.observe('loaded', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -369,11 +372,11 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after save` hook', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.create({ name: 'created' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: instance.id, name: 'created', @@ -409,22 +412,22 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('sends `after save` for each model in an array', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.create( [{ name: '1' }, { name: '2' }], function(err, list) { if (err) return done(err); // Creation of multiple instances is executed in parallel - observedContexts.sort(function(c1, c2) { + ctxRecorder.records.sort(function(c1, c2) { return c1.instance.name - c2.instance.name; }); - observedContexts.should.eql([ - aTestModelCtx({ + ctxRecorder.records.should.eql([ + aCtxForModel(TestModel, { instance: { id: list[0].id, name: '1', extra: undefined }, isNewInstance: true }), - aTestModelCtx({ + aCtxForModel(TestModel, { instance: { id: list[1].id, name: '2', extra: undefined }, isNewInstance: true }), @@ -441,7 +444,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { next(); }); - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.create( [{ name: 'ok' }, { name: 'fail' }], @@ -454,7 +457,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { // the models that were not created due to an error. list.map(get('name')).should.eql(['ok', 'fail']); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: list[0].id, name: 'ok', extra: undefined }, isNewInstance: true })); @@ -465,14 +468,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { describe('PersistedModel.findOrCreate', function() { it('triggers `access` hook', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.findOrCreate( { where: { name: 'new-record' } }, { name: 'new-record' }, function(err, record, created) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ query: { + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: { where: { name: 'new-record' }, limit: 1, offset: 0, @@ -484,7 +487,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (dataSource.connector.findOrCreate) { it('triggers `before save` hook when found', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); TestModel.findOrCreate( { where: { name: existingInstance.name } }, @@ -492,7 +495,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { function(err, record, created) { if (err) return done(err); record.id.should.eql(existingInstance.id); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: getLastGeneratedUid(), name: existingInstance.name, @@ -506,14 +509,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { } it('triggers `before save` hook when not found', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); TestModel.findOrCreate( { where: { name: 'new-record' } }, { name: 'new-record' }, function(err, record, created) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: record.id, name: 'new-record', @@ -609,7 +612,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (dataSource.connector.findOrCreate) { it('triggers `persist` hook when found', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); TestModel.findOrCreate( { where: { name: existingInstance.name } }, @@ -625,7 +628,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { // Hence, the test below asserts both `data.id` and // `currentInstance.id` to match getLastGeneratedUid(). // On same lines, it also asserts `isNewInstance` to be true. - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: getLastGeneratedUid(), name: existingInstance.name @@ -645,7 +648,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { } it('triggers `persist` hook when not found', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); TestModel.findOrCreate( { where: { name: 'new-record' } }, @@ -656,7 +659,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { // `context.where` is present in Optimized connector context, // but, unoptimized connector does NOT have it. if (dataSource.connector.findOrCreate) { - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: record.id, name: 'new-record' @@ -670,7 +673,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { where: { name: 'new-record' } })); } else { - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: record.id, name: 'new-record' @@ -685,7 +688,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (dataSource.connector.findOrCreate) { it('applies updates from `persist` hook when found', function(done) { - TestModel.observe('persist', pushContextAndNext(function(ctx){ + TestModel.observe('persist', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -718,7 +721,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { } it('applies updates from `persist` hook when not found', function(done) { - TestModel.observe('persist', pushContextAndNext(function(ctx){ + TestModel.observe('persist', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -756,7 +759,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (dataSource.connector.findOrCreate) { it('triggers `loaded` hook when found', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); TestModel.findOrCreate( { where: { name: existingInstance.name } }, @@ -769,7 +772,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { // After the call to `connector.findOrCreate`, since the record // already exists, `data.id` matches `existingInstance.id` // as against the behaviour noted for `persist` hook - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: existingInstance.id, name: existingInstance.name @@ -783,7 +786,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { } it('triggers `loaded` hook when not found', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); TestModel.findOrCreate( { where: { name: 'new-record' } }, @@ -791,7 +794,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { function(err, record, created) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: record.id, name: 'new-record' @@ -816,7 +819,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (dataSource.connector.findOrCreate) { it('applies updates from `loaded` hook when found', function(done) { - TestModel.observe('loaded', pushContextAndNext(function(ctx){ + TestModel.observe('loaded', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -834,7 +837,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { } it('applies updates from `loaded` hook when not found', function(done) { - TestModel.observe('loaded', pushContextAndNext(function(ctx){ + TestModel.observe('loaded', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -858,14 +861,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after save` hook when not found', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.findOrCreate( { where: { name: 'new name' } }, { name: 'new name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: instance.id, name: 'new name', @@ -878,14 +881,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('does not trigger `after save` hook when found', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.findOrCreate( { where: { id: existingInstance.id } }, { name: existingInstance.name }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql("hook not called"); + ctxRecorder.records.should.eql('hook not called'); done(); }); }); @@ -893,12 +896,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { describe('PersistedModel.count', function(done) { it('triggers `access` hook', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.count({ id: existingInstance.id }, function(err, count) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ query: { - where: { id: existingInstance.id } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: { + where: { id: existingInstance.id }, }})); done(); }); @@ -936,12 +939,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before save` hook', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); existingInstance.name = 'changed'; existingInstance.save(function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ instance: { + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: existingInstance.id, name: 'changed', extra: undefined, @@ -984,16 +987,16 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); existingInstance.name = 'changed'; existingInstance.save(function(err, instance) { if (err) return done(err); // HACK: extra is undefined for NoSQL and null for SQL - delete observedContexts.data.extra; - delete observedContexts.currentInstance.extra; - observedContexts.should.eql(aTestModelCtx({ + delete ctxRecorder.records.data.extra; + delete ctxRecorder.records.currentInstance.extra; + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: existingInstance.id, name: 'changed' @@ -1011,7 +1014,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies updates from `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext(function(ctx){ + TestModel.observe('persist', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -1023,13 +1026,13 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `loaded` hook', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); existingInstance.extra = 'changed'; existingInstance.save(function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: existingInstance.id, name: existingInstance.name, @@ -1053,7 +1056,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies updates from `loaded` hook', function(done) { - TestModel.observe('loaded', pushContextAndNext(function(ctx){ + TestModel.observe('loaded', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -1065,12 +1068,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after save` hook on update', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); existingInstance.name = 'changed'; existingInstance.save(function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: existingInstance.id, name: 'changed', @@ -1084,7 +1087,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after save` hook on create', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); var instance = new TestModel( { id: 'new-id', name: 'created' }, @@ -1092,7 +1095,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { instance.save(function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: instance.id, name: 'created', @@ -1148,14 +1151,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before save` hook', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); var currentInstance = deepCloneToObject(existingInstance); existingInstance.updateAttributes({ name: 'changed' }, function(err) { if (err) return done(err); existingInstance.name.should.equal('changed'); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { id: existingInstance.id }, data: { name: 'changed' }, currentInstance: currentInstance @@ -1208,11 +1211,11 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); existingInstance.updateAttributes({ name: 'changed' }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { id: existingInstance.id }, data: { name: 'changed' }, currentInstance: { @@ -1228,7 +1231,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies updates from `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext(function(ctx){ + TestModel.observe('persist', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -1265,8 +1268,8 @@ module.exports = function(dataSource, should, connectorCapabilities) { var existingUser = instance; - User.observe('persist', pushContextAndNext(function(ctx) { - should.exist(ctx.data.address) + User.observe('persist', ctxRecorder.recordAndNext(function(ctx) { + should.exist(ctx.data.address); ctx.data.address.should.be.type('object'); ctx.data.address.should.not.be.instanceOf(Address); @@ -1301,12 +1304,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `loaded` hook', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); existingInstance.updateAttributes({ name: 'changed' }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - data: { name: 'changed' } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + data: { name: 'changed' }, })); done(); @@ -1324,7 +1327,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies updates from `loaded` hook updateAttributes', function(done) { - TestModel.observe('loaded', pushContextAndNext(function(ctx){ + TestModel.observe('loaded', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -1341,12 +1344,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after save` hook', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); existingInstance.name = 'changed'; existingInstance.updateAttributes({ name: 'changed' }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: existingInstance.id, name: 'changed', @@ -1381,7 +1384,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); }); }); - + if (!getSchema().connector.replaceById) { describe.skip('replaceById - not implemented', function(){}); } else { @@ -1404,15 +1407,15 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before save` hook', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); existingInstance.replaceAttributes({ name: 'changed' }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: existingInstance.id, name: 'changed', - extra: undefined, + extra: undefined, }, isNewInstance: false })); @@ -1462,11 +1465,11 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); existingInstance.replaceAttributes({ name: 'replacedName' }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { id: existingInstance.id }, data: { name: 'replacedName', @@ -1485,7 +1488,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies delete from `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext(function(ctx){ + TestModel.observe('persist', ctxRecorder.recordAndNext(function(ctx) { delete ctx.data.extra; })); @@ -1494,7 +1497,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { instance.should.not.have.property('extra', 'hook data'); done(); }); - }); + }); it('applies updates from `persist` hook - for nested model instance', function(done) { var Address = dataSource.createModel('NestedAddress', { @@ -1517,8 +1520,8 @@ module.exports = function(dataSource, should, connectorCapabilities) { var existingUser = instance; - User.observe('persist', pushContextAndNext(function(ctx) { - should.exist(ctx.data.address) + User.observe('persist', ctxRecorder.recordAndNext(function(ctx) { + should.exist(ctx.data.address); ctx.data.address.should.be.type('object'); ctx.data.address.should.not.be.instanceOf(Address); @@ -1548,12 +1551,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `loaded` hook', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); existingInstance.replaceAttributes({ name: 'changed' }, function(err, data) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - data: { + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + data: { name: 'changed', id: data.id }, @@ -1574,7 +1577,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies updates from `loaded` hook replaceAttributes', function(done) { - TestModel.observe('loaded', pushContextAndNext(function(ctx){ + TestModel.observe('loaded', ctxRecorder.recordAndNext(function(ctx) { ctx.data.name = 'changed in hook'; })); @@ -1583,15 +1586,15 @@ module.exports = function(dataSource, should, connectorCapabilities) { instance.should.have.property('name', 'changed in hook'); done(); }); - }); + }); it('triggers `after save` hook', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); existingInstance.name = 'replaced'; existingInstance.replaceAttributes({ name: 'replaced' }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: existingInstance.id, name: 'replaced', @@ -1624,8 +1627,8 @@ module.exports = function(dataSource, should, connectorCapabilities) { instance.should.have.property('extra', 'hook data'); done(); }); - }); - }); + }); + }); } describe('PersistedModel.updateOrCreate', function() { @@ -1677,41 +1680,41 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `access` hook on create', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: 'not-found', name: 'not found' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ query: { - where: { id: 'not-found' } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: { + where: { id: 'not-found' }, }})); done(); }); }); it('triggers `access` hook on update', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: existingInstance.id, name: 'new name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ query: { - where: { id: existingInstance.id } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: { + where: { id: existingInstance.id }, }})); done(); }); }); it('does not trigger `access` on missing id', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { name: 'new name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.equal('hook not called'); + ctxRecorder.records.should.equal('hook not called'); done(); }); }); @@ -1778,7 +1781,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before save` hook on update', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: existingInstance.id, name: 'updated name' }, @@ -1788,7 +1791,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { // Atomic implementations of `updateOrCreate` cannot // provide full instance as that depends on whether // UPDATE or CREATE will be triggered - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { id: existingInstance.id }, data: { id: existingInstance.id, name: 'updated name' } })); @@ -1796,7 +1799,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { // currentInstance is set, because a non-atomic `updateOrCreate` // will use `prototype.updateAttributes` internally, which // exposes this to the context - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { id: existingInstance.id }, data: { id: existingInstance.id, name: 'updated name' }, currentInstance: existingInstance @@ -1807,7 +1810,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before save` hook on create', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: 'new-id', name: 'a name' }, @@ -1818,14 +1821,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { // Atomic implementations of `updateOrCreate` cannot // provide full instance as that depends on whether // UPDATE or CREATE will be triggered - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { id: 'new-id' }, data: { id: 'new-id', name: 'a name' } })); } else { // The default unoptimized implementation runs // `instance.save` and thus a full instance is availalbe - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: 'new-id', name: 'a name', extra: undefined }, isNewInstance: true })); @@ -1898,7 +1901,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `persist` hook on create', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: 'new-id', name: 'a name' }, @@ -1906,7 +1909,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (err) return done(err); if (dataSource.connector.updateOrCreate) { - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { id: 'new-id' }, data: { id: 'new-id', name: 'a name' }, currentInstance: { @@ -1916,7 +1919,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { } })); } else { - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: 'new-id', name: 'a name' @@ -1934,14 +1937,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `persist` hook on update', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: existingInstance.id, name: 'updated name' }, function(err, instance) { if (err) return done(err); - var expectedContext = aTestModelCtx({ + var expectedContext = aCtxForModel(TestModel, { where: { id: existingInstance.id }, data: { id: existingInstance.id, @@ -1960,13 +1963,13 @@ module.exports = function(dataSource, should, connectorCapabilities) { expectedContext.isNewInstance = false; } - observedContexts.should.eql(expectedContext); + ctxRecorder.records.should.eql(expectedContext); done(); }); }); it('triggers `loaded` hook on create', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: 'new-id', name: 'a name' }, @@ -1974,11 +1977,11 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (err) return done(err); if (dataSource.connector.updateOrCreate) { - observedContexts.should.eql(aTestModelCtx({ - data: { id: 'new-id', name: 'a name' } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + data: { id: 'new-id', name: 'a name' }, })); } else { - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: 'new-id', name: 'a name' @@ -1991,7 +1994,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `loaded` hook on update', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: existingInstance.id, name: 'updated name' }, @@ -1999,18 +2002,18 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (err) return done(err); if (dataSource.connector.updateOrCreate) { - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { id: existingInstance.id, name: 'updated name' } })); } else { - // For Unoptimized connector, the callback function `pushContextAndNext` - // is called twice. As a result, observedContexts + // For Unoptimized connector, the callback function `contextRecorder.recordAndNext` + // is called twice. As a result, contextRecorder.records // returns an array and NOT a single instance. - observedContexts.should.eql([ - aTestModelCtx({ + ctxRecorder.records.should.eql([ + aCtxForModel(TestModel, { instance: { id: existingInstance.id, name: 'first', @@ -2019,7 +2022,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { isNewInstance: false, options: { notify: false } }), - aTestModelCtx({ + aCtxForModel(TestModel, { data: { id: existingInstance.id, name: 'updated name' @@ -2042,13 +2045,13 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after save` hook on update', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: existingInstance.id, name: 'updated name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: existingInstance.id, name: 'updated name', @@ -2061,13 +2064,13 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after save` hook on create', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.updateOrCreate( { id: 'new-id', name: 'a name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { instance: { id: instance.id, name: 'a name', @@ -2116,16 +2119,16 @@ module.exports = function(dataSource, should, connectorCapabilities) { 'persist', 'loaded', 'after save' - ]); + ]); } else { // TODO: Please see loopback-datasource-juggler/issues#836 - // + // // loaded hook is triggered twice in non-atomic version: // 1) It gets triggered once by "find()" in this chain: - // "replaceORCreate()->findOne()->find()", - // which is a bug; Please see this ticket: + // "replaceORCreate()->findOne()->find()", + // which is a bug; Please see this ticket: // loopback-datasource-juggler/issues#836. - // 2) It, also, gets triggered in "replaceAttributes()" + // 2) It, also, gets triggered in "replaceAttributes()" // in this chain replaceORCreate()->replaceAttributes() triggered.should.eql([ 'access', @@ -2141,41 +2144,41 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `access` hook on create', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: 'not-found', name: 'not found' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ query: { - where: { id: 'not-found' } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: { + where: { id: 'not-found' }, }})); done(); }); }); it('triggers `access` hook on replace', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: existingInstance.id, name: 'new name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ query: { - where: { id: existingInstance.id } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: { + where: { id: existingInstance.id }, }})); done(); }); }); it('does not trigger `access` on missing id', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { name: 'new name' }, function(err, instance) { if (err) return done(err); - observedContexts.should.equal('hook not called'); + ctxRecorder.records.should.equal('hook not called'); done(); }); }); @@ -2242,14 +2245,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before save` hookon create', function(done) { - TestModel.observe('before save', pushContextAndNext()); - TestModel.replaceOrCreate({id: existingInstance.id, name: 'new name'}, + TestModel.observe('before save', ctxRecorder.recordAndNext()); + TestModel.replaceOrCreate({ id: existingInstance.id, name: 'new name' }, function(err, instance) { if (err) return done(err); - var expectedContext = aTestModelCtx({ - instance: instance + var expectedContext = aCtxForModel(TestModel, { + instance: instance, }); if (!dataSource.connector.replaceOrCreate) { @@ -2257,16 +2260,16 @@ module.exports = function(dataSource, should, connectorCapabilities) { } done(); }); - }); + }); it('triggers `before save` hook on replace', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: existingInstance.id, name: 'replaced name' }, function(err, instance) { if (err) return done(err); - var expectedContext = aTestModelCtx({ + var expectedContext = aCtxForModel(TestModel, { instance: { id: existingInstance.id, name: 'replaced name', @@ -2277,21 +2280,21 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (!dataSource.connector.replaceOrCreate) { expectedContext.isNewInstance = false; } - observedContexts.should.eql(expectedContext); + ctxRecorder.records.should.eql(expectedContext); done(); }); }); it('triggers `before save` hook on create', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: 'new-id', name: 'a name' }, function(err, instance) { if (err) return done(err); - var expectedContext = aTestModelCtx({ + var expectedContext = aCtxForModel(TestModel, { instance: { id: 'new-id', name: 'a name', @@ -2302,7 +2305,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (!dataSource.connector.replaceOrCreate) { expectedContext.isNewInstance = true; } - observedContexts.should.eql(expectedContext); + ctxRecorder.records.should.eql(expectedContext); done(); }); @@ -2336,25 +2339,24 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `persist` hook on create', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: 'new-id', name: 'a name' }, function(err, instance) { if (err) return done(err); - var expectedContext = aTestModelCtx({ - currentInstance: { - id: 'new-id', - name: 'a name', - extra: undefined - }, data: { - id: 'new-id', - name: 'a name' - } - }); - - + var expectedContext = aCtxForModel(TestModel, { + currentInstance: { + id: 'new-id', + name: 'a name', + extra: undefined, + }, + data: { + id: 'new-id', + name: 'a name', + }, + }); if (dataSource.connector.replaceOrCreate) { expectedContext.where = { id: 'new-id' }; @@ -2364,13 +2366,13 @@ module.exports = function(dataSource, should, connectorCapabilities) { // are not records to match where filter. expectedContext.isNewInstance = true; } - observedContexts.should.eql(expectedContext); + ctxRecorder.records.should.eql(expectedContext); done(); }); }); it('triggers `persist` hook on replace', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: existingInstance.id, name: 'replaced name' }, @@ -2390,44 +2392,44 @@ module.exports = function(dataSource, should, connectorCapabilities) { } }; - var expectedContext = aTestModelCtx(expected); + var expectedContext = aCtxForModel(TestModel, expected); var expectedContext; if (!dataSource.connector.replaceOrCreate) { expectedContext.isNewInstance = false; } - observedContexts.should.eql(expectedContext); + ctxRecorder.records.should.eql(expectedContext); done(); }); }); it('triggers `loaded` hook on create', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: 'new-id', name: 'a name' }, function(err, instance) { if (err) return done(err); - + var expected = { data: { id: 'new-id', name: 'a name' } }; - - expected.isNewInstance = + + expected.isNewInstance = connectorCapabilities.replaceOrCreateReportsNewInstance ? - true : undefined; - - observedContexts.should.eql(aTestModelCtx(expected)); + true : undefined; + + ctxRecorder.records.should.eql(aCtxForModel(TestModel, expected)); done(); }); }); it('triggers `loaded` hook on replace', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: existingInstance.id, name: 'replaced name' }, @@ -2440,25 +2442,25 @@ module.exports = function(dataSource, should, connectorCapabilities) { name: 'replaced name' } }; - - expected.isNewInstance = + + expected.isNewInstance = connectorCapabilities.replaceOrCreateReportsNewInstance ? false : undefined; if (dataSource.connector.replaceOrCreate) { - observedContexts.should.eql(aTestModelCtx(expected)); + ctxRecorder.records.should.eql(aCtxForModel(TestModel, expected)); } else { // TODO: Please see loopback-datasource-juggler/issues#836 - // + // // loaded hook is triggered twice in non-atomic version: // 1) It gets triggered once by "find()" in this chain: - // "replaceORCreate()->findOne()->find()", - // which is a bug; Please see this ticket: + // "replaceORCreate()->findOne()->find()", + // which is a bug; Please see this ticket: // loopback-datasource-juggler/issues#836. - // 2) It, also, gets triggered in "replaceAttributes()" + // 2) It, also, gets triggered in "replaceAttributes()" // in this chain replaceORCreate()->replaceAttributes() - observedContexts.should.eql([ - aTestModelCtx({ + ctxRecorder.records.should.eql([ + aCtxForModel(TestModel, { data: { id: existingInstance.id, name: 'first' @@ -2466,7 +2468,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { isNewInstance: false, options: { notify: false } }), - aTestModelCtx({ + aCtxForModel(TestModel, { data: { id: existingInstance.id, name: 'replaced name' @@ -2487,16 +2489,16 @@ module.exports = function(dataSource, should, connectorCapabilities) { [err].should.eql([expectedError]); done(); }); - }); + }); it('triggers `after save` hook on replace', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: existingInstance.id, name: 'replaced name' }, function(err, instance) { if (err) return done(err); - + var expected = { instance: { id: existingInstance.id, @@ -2504,24 +2506,24 @@ module.exports = function(dataSource, should, connectorCapabilities) { extra: undefined } }; - - expected.isNewInstance = + + expected.isNewInstance = connectorCapabilities.replaceOrCreateReportsNewInstance ? false : undefined; - observedContexts.should.eql(aTestModelCtx(expected)); + ctxRecorder.records.should.eql(aCtxForModel(TestModel, expected)); done(); }); }); it('triggers `after save` hook on create', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.replaceOrCreate( { id: 'new-id', name: 'a name' }, function(err, instance) { if (err) return done(err); - + var expected = { instance: { id: instance.id, @@ -2529,11 +2531,11 @@ module.exports = function(dataSource, should, connectorCapabilities) { extra: undefined } }; - expected.isNewInstance = + expected.isNewInstance = connectorCapabilities.replaceOrCreateReportsNewInstance ? true : undefined; - - observedContexts.should.eql(aTestModelCtx(expected)); + + ctxRecorder.records.should.eql(aCtxForModel(TestModel, expected)); done(); }); }); @@ -2542,23 +2544,23 @@ module.exports = function(dataSource, should, connectorCapabilities) { describe('PersistedModel.deleteAll', function() { it('triggers `access` hook with query', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.deleteAll({ name: existingInstance.name }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - query: { where: { name: existingInstance.name } } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + query: { where: { name: existingInstance.name }}, })); done(); }); }); it('triggers `access` hook without query', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.deleteAll(function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ query: { where: {} } })); + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: { where: {}}})); done(); }); }); @@ -2580,23 +2582,23 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before delete` hook with query', function(done) { - TestModel.observe('before delete', pushContextAndNext()); + TestModel.observe('before delete', ctxRecorder.recordAndNext()); TestModel.deleteAll({ name: existingInstance.name }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - where: { name: existingInstance.name } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + where: { name: existingInstance.name }, })); done(); }); }); it('triggers `before delete` hook without query', function(done) { - TestModel.observe('before delete', pushContextAndNext()); + TestModel.observe('before delete', ctxRecorder.recordAndNext()); TestModel.deleteAll(function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ where: {} })); + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: {}})); done(); }); }); @@ -2632,22 +2634,22 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after delete` hook without query', function(done) { - TestModel.observe('after delete', pushContextAndNext()); + TestModel.observe('after delete', ctxRecorder.recordAndNext()); TestModel.deleteAll(function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ where: {} })); + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: {}})); done(); }); }); it('triggers `after delete` hook without query', function(done) { - TestModel.observe('after delete', pushContextAndNext()); + TestModel.observe('after delete', ctxRecorder.recordAndNext()); TestModel.deleteAll({ name: existingInstance.name }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - where: { name: existingInstance.name } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + where: { name: existingInstance.name }, })); done(); }); @@ -2665,12 +2667,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { describe('PersistedModel.prototype.delete', function() { it('triggers `access` hook', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); existingInstance.delete(function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - query: { where: { id: existingInstance.id } } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + query: { where: { id: existingInstance.id }}, })); done(); }); @@ -2693,13 +2695,13 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before delete` hook', function(done) { - TestModel.observe('before delete', pushContextAndNext()); + TestModel.observe('before delete', ctxRecorder.recordAndNext()); existingInstance.delete(function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - where: { id: existingInstance.id }, - instance: existingInstance + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + where: { id: existingInstance.id }, + instance: existingInstance, })); done(); }); @@ -2736,11 +2738,11 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after delete` hook', function(done) { - TestModel.observe('after delete', pushContextAndNext()); + TestModel.observe('after delete', ctxRecorder.recordAndNext()); existingInstance.delete(function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { id: existingInstance.id }, instance: existingInstance })); @@ -2749,12 +2751,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `after delete` hook without query', function(done) { - TestModel.observe('after delete', pushContextAndNext()); + TestModel.observe('after delete', ctxRecorder.recordAndNext()); TestModel.deleteAll({ name: existingInstance.name }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ - where: { name: existingInstance.name } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { + where: { name: existingInstance.name }, })); done(); }); @@ -2770,24 +2772,24 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('propagates hookState from `before delete` to `after delete`', function(done) { - TestModel.observe('before delete', pushContextAndNext(function(ctx) { + TestModel.observe('before delete', ctxRecorder.recordAndNext(function(ctx) { ctx.hookState.foo = 'bar'; })); - TestModel.observe('after delete', pushContextAndNext(function(ctx) { + TestModel.observe('after delete', ctxRecorder.recordAndNext(function(ctx) { ctx.hookState.foo = ctx.hookState.foo.toUpperCase(); })); existingInstance.delete(function(err) { if (err) return done(err); - observedContexts.should.eql([ - aTestModelCtx({ - hookState: { foo: 'bar', test: true }, + ctxRecorder.records.should.eql([ + aCtxForModel(TestModel, { + hookState: { foo: 'bar' }, where: { id: '1' }, instance: existingInstance }), - aTestModelCtx({ - hookState: { foo: 'BAR', test: true }, + aCtxForModel(TestModel, { + hookState: { foo: 'BAR' }, where: { id: '1' }, instance: existingInstance }) @@ -2814,15 +2816,15 @@ module.exports = function(dataSource, should, connectorCapabilities) { describe('PersistedModel.updateAll', function() { it('triggers `access` hook', function(done) { - TestModel.observe('access', pushContextAndNext()); + TestModel.observe('access', ctxRecorder.recordAndNext()); TestModel.updateAll( { name: 'searched' }, { name: 'updated' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ query: { - where: { name: 'searched' } + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { query: { + where: { name: 'searched' }, }})); done(); }); @@ -2851,14 +2853,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `before save` hook', function(done) { - TestModel.observe('before save', pushContextAndNext()); + TestModel.observe('before save', ctxRecorder.recordAndNext()); TestModel.updateAll( { name: 'searched' }, { name: 'updated' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { name: 'searched' }, data: { name: 'updated' }, })); @@ -2887,7 +2889,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext()); + TestModel.observe('persist', ctxRecorder.recordAndNext()); TestModel.updateAll( { name: existingInstance.name }, @@ -2895,7 +2897,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { function(err, instance) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { data: { name: 'changed' }, where: { name: existingInstance.name } })); @@ -2905,7 +2907,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('applies updates from `persist` hook', function(done) { - TestModel.observe('persist', pushContextAndNext(function(ctx){ + TestModel.observe('persist', ctxRecorder.recordAndNext(function(ctx) { ctx.data.extra = 'hook data'; })); @@ -2922,27 +2924,27 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('does not trigger `loaded`', function(done) { - TestModel.observe('loaded', pushContextAndNext()); + TestModel.observe('loaded', ctxRecorder.recordAndNext()); TestModel.updateAll( { id: existingInstance.id }, { name: 'changed' }, function(err, instance) { if (err) return done(err); - observedContexts.should.eql("hook not called"); + ctxRecorder.records.should.eql('hook not called'); done(); }); }); it('triggers `after save` hook', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.updateAll( { id: existingInstance.id }, { name: 'updated name' }, function(err) { if (err) return done(err); - observedContexts.should.eql(aTestModelCtx({ + ctxRecorder.records.should.eql(aCtxForModel(TestModel, { where: { id: existingInstance.id }, data: { name: 'updated name' } })); @@ -2951,7 +2953,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('accepts hookState from options', function(done) { - TestModel.observe('after save', pushContextAndNext()); + TestModel.observe('after save', ctxRecorder.recordAndNext()); TestModel.updateAll( { id: existingInstance.id }, @@ -2959,8 +2961,8 @@ module.exports = function(dataSource, should, connectorCapabilities) { { foo: 'bar' }, function(err) { if (err) return done(err); - observedContexts.options.should.eql({ - foo: 'bar' + ctxRecorder.records.options.should.eql({ + foo: 'bar', }); done(); }); @@ -2968,29 +2970,6 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); - function pushContextAndNext(fn) { - return function(context, next) { - if (typeof fn === 'function') { - fn(context); - } - - context = deepCloneToObject(context); - context.hookState.test = true; - - if (typeof observedContexts === 'string') { - observedContexts = context; - return next(); - } - - if (!Array.isArray(observedContexts)) { - observedContexts = [observedContexts]; - } - - observedContexts.push(context); - next(); - }; - } - function pushNameAndNext(name) { return function(context, next) { observersCalled.push(name); @@ -3015,17 +2994,6 @@ module.exports = function(dataSource, should, connectorCapabilities) { }; } - function aTestModelCtx(ctx) { - ctx.Model = TestModel; - if (!ctx.hookState) { - ctx.hookState = { test: true }; - } - if (!ctx.options) { - ctx.options = {}; - } - return deepCloneToObject(ctx); - } - function findTestModels(query, cb) { if (cb === undefined && typeof query === 'function') { cb = query; @@ -3058,18 +3026,6 @@ module.exports = function(dataSource, should, connectorCapabilities) { } }); - function deepCloneToObject(obj) { - return traverse(obj).map(function(x) { - if (x === undefined) { - // RDBMSs return null - return null; - } - if (x && x.toObject) - return x.toObject(true); - if (x && typeof x === 'function' && x.modelName) - return '[ModelCtor ' + x.modelName + ']'; - }); - } function get(propertyName) { return function(obj) { From 90f04181a49a78337585fb2985033acb0428112d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 12 Apr 2016 14:32:34 +0200 Subject: [PATCH 2/3] test: extract uid-generator helper --- test/helpers/uid-generator.js | 19 +++++++++++++++++++ test/persistence-hooks.suite.js | 24 +++++++++--------------- 2 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 test/helpers/uid-generator.js diff --git a/test/helpers/uid-generator.js b/test/helpers/uid-generator.js new file mode 100644 index 00000000..f2bc95b4 --- /dev/null +++ b/test/helpers/uid-generator.js @@ -0,0 +1,19 @@ +// Copyright IBM Corp. 2016. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +var lastId = 0; + +exports.next = function() { + lastId++; + return exports.last(); +}; + +exports.last = function() { + return '' + lastId; +}; + +exports.reset = function() { + lastId = 0; +}; diff --git a/test/persistence-hooks.suite.js b/test/persistence-hooks.suite.js index ebcc06d8..94e686ca 100644 --- a/test/persistence-hooks.suite.js +++ b/test/persistence-hooks.suite.js @@ -10,6 +10,9 @@ var ContextRecorder = contextTestHelpers.ContextRecorder; var deepCloneToObject = contextTestHelpers.deepCloneToObject; var aCtxForModel = contextTestHelpers.aCtxForModel; +var uid = require('./helpers/uid-generator'); +var getLastGeneratedUid = uid.last; + module.exports = function(dataSource, should, connectorCapabilities) { if (!connectorCapabilities) connectorCapabilities = {}; if (connectorCapabilities.replaceOrCreateReportsNewInstance === undefined) { @@ -18,7 +21,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { describe('Persistence hooks', function() { var ctxRecorder, expectedError, observersCalled; var TestModel, existingInstance; - var migrated = false, lastId; + var migrated = false; var triggered; var undefinedValue = undefined; @@ -30,12 +33,12 @@ module.exports = function(dataSource, should, connectorCapabilities) { TestModel = dataSource.createModel('TestModel', { // Set id.generated to false to honor client side values - id: { type: String, id: true, generated: false, default: uid }, + id: { type: String, id: true, generated: false, default: uid.next }, name: { type: String, required: true }, extra: { type: String, required: false } }); - lastId = 0; + uid.reset(); if (migrated) { TestModel.deleteAll(done); @@ -226,7 +229,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { next(); }); - TestModel.create({ id: uid(), name: 'a-name' }, function(err, instance) { + TestModel.create({ id: uid.next(), name: 'a-name' }, function(err, instance) { if (err) return done(err); instance.should.have.property('extra', 'hook data'); done(); @@ -1255,7 +1258,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); var User = dataSource.createModel('UserWithAddress', { - id: { type: String, id: true, default: uid() }, + id: { type: String, id: true, default: uid.next }, name: { type: String, required: true }, address: {type: Address, required: false}, extra: {type: String} @@ -1507,7 +1510,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); var User = dataSource.createModel('UserWithAddress', { - id: { type: String, id: true, default: uid() }, + id: { type: String, id: true, default: uid.next }, name: { type: String, required: true }, address: {type: Address, required: false}, extra: {type: String} @@ -3007,15 +3010,6 @@ module.exports = function(dataSource, should, connectorCapabilities) { TestModel.findOne({ where: { id: id } }, { notify: false }, cb); } - function uid() { - lastId += 1; - return '' + lastId; - } - - function getLastGeneratedUid() { - return '' + lastId; - } - function monitorHookExecution() { triggered = []; TestModel._notify = TestModel.notifyObserversOf; From 7d7662bfef25a3ec4a207a07267c1dee3e9c092e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 12 Apr 2016 15:06:42 +0200 Subject: [PATCH 3/3] test: extract hook-monitor helper --- test/helpers/hook-monitor.js | 30 +++++++++++++++ test/persistence-hooks.suite.js | 66 +++++++++++++-------------------- 2 files changed, 56 insertions(+), 40 deletions(-) create mode 100644 test/helpers/hook-monitor.js diff --git a/test/helpers/hook-monitor.js b/test/helpers/hook-monitor.js new file mode 100644 index 00000000..384d076b --- /dev/null +++ b/test/helpers/hook-monitor.js @@ -0,0 +1,30 @@ +// Copyright IBM Corp. 2016. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +module.exports = HookMonitor; + +function HookMonitor(opts) { + if (!(this instanceof HookMonitor)) { + return new HookMonitor(); + } + + this.options = opts || {}; + this.names = []; +}; + +HookMonitor.prototype.install = function(ObservedModel, hookNames) { + var monitor = this; + this.names = []; + ObservedModel._notify = ObservedModel.notifyObserversOf; + ObservedModel.notifyObserversOf = function(operation, context, callback) { + if (!Array.isArray(hookNames) || hookNames.indexOf(operation) !== -1) { + var item = monitor.options.includeModelName ? + ObservedModel.modelName + ':' + operation : + operation; + monitor.names.push(item); + } + this._notify.apply(this, arguments); + }; +}; diff --git a/test/persistence-hooks.suite.js b/test/persistence-hooks.suite.js index 94e686ca..5c050e85 100644 --- a/test/persistence-hooks.suite.js +++ b/test/persistence-hooks.suite.js @@ -13,23 +13,24 @@ var aCtxForModel = contextTestHelpers.aCtxForModel; var uid = require('./helpers/uid-generator'); var getLastGeneratedUid = uid.last; +var HookMonitor = require('./helpers/hook-monitor'); + module.exports = function(dataSource, should, connectorCapabilities) { if (!connectorCapabilities) connectorCapabilities = {}; if (connectorCapabilities.replaceOrCreateReportsNewInstance === undefined) { console.warn('The connector does not support a recently added feature: replaceOrCreateReportsNewInstance'); } describe('Persistence hooks', function() { - var ctxRecorder, expectedError, observersCalled; + var ctxRecorder, hookMonitor, expectedError; var TestModel, existingInstance; var migrated = false; - var triggered; var undefinedValue = undefined; beforeEach(function setupDatabase(done) { ctxRecorder = new ContextRecorder('hook not called'); + hookMonitor = new HookMonitor({ includeModelName: false }); expectedError = new Error('test error'); - observersCalled = []; TestModel = dataSource.createModel('TestModel', { // Set id.generated to false to honor client side values @@ -76,7 +77,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { function(err, list) { if (err) return done(err); - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'loaded' ]); @@ -186,7 +187,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { function(err, record, created) { if (err) return done(err); - triggered.should.eql([ + hookMonitor.names.should.eql([ 'before save', 'persist', 'loaded', @@ -552,7 +553,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { { name: 'new-record' }, function(err, record, created) { if (err) return done(err); - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'before save', 'persist', @@ -573,14 +574,14 @@ module.exports = function(dataSource, should, connectorCapabilities) { if (err) return done(err); if (dataSource.connector.findOrCreate) { - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'before save', 'persist', 'loaded' ]); } else { - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'loaded' ]); @@ -931,7 +932,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { existingInstance.save( function(err, record, created) { if (err) return done(err); - triggered.should.eql([ + hookMonitor.names.should.eql([ 'before save', 'persist', 'loaded', @@ -1143,7 +1144,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { { name: 'changed' }, function(err, record, created) { if (err) return done(err); - triggered.should.eql([ + hookMonitor.names.should.eql([ 'before save', 'persist', 'loaded', @@ -1399,7 +1400,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { { name: 'replaced' }, function(err, record, created) { if (err) return done(err); - triggered.should.eql([ + hookMonitor.names.should.eql([ 'before save', 'persist', 'loaded', @@ -1642,7 +1643,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { { id: 'not-found', name: 'not found' }, function(err, record, created) { if (err) return done(err); - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'before save', 'persist', @@ -1661,7 +1662,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { function(err, record, created) { if (err) return done(err); if (dataSource.connector.updateOrCreate) { - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'before save', 'persist', @@ -1669,7 +1670,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { 'after save' ]); } else { - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'loaded', 'before save', @@ -1766,8 +1767,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers hooks only once', function(done) { - TestModel.observe('access', pushNameAndNext('access')); - TestModel.observe('before save', pushNameAndNext('before save')); + monitorHookExecution(['access', 'before save']); TestModel.observe('access', function(ctx, next) { ctx.query = { where: { id: { neq: existingInstance.id } } }; @@ -1778,7 +1778,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { { id: 'ignored', name: 'new name' }, function(err, instance) { if (err) return done(err); - observersCalled.should.eql(['access', 'before save']); + hookMonitor.names.should.eql(['access', 'before save']); done(); }); }); @@ -2097,7 +2097,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { { id: 'not-found', name: 'not found' }, function(err, record, created) { if (err) return done(err); - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'before save', 'persist', @@ -2116,7 +2116,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { function(err, record, created) { if (err) return done(err); if (dataSource.connector.replaceOrCreate) { - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'before save', 'persist', @@ -2133,7 +2133,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { // loopback-datasource-juggler/issues#836. // 2) It, also, gets triggered in "replaceAttributes()" // in this chain replaceORCreate()->replaceAttributes() - triggered.should.eql([ + hookMonitor.names.should.eql([ 'access', 'loaded', 'before save', @@ -2230,8 +2230,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers hooks only once', function(done) { - TestModel.observe('access', pushNameAndNext('access')); - TestModel.observe('before save', pushNameAndNext('before save')); + monitorHookExecution(['access', 'before save']); TestModel.observe('access', function(ctx, next) { ctx.query = { where: { id: { neq: existingInstance.id } } }; @@ -2242,7 +2241,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { { id: 'ignored', name: 'new name' }, function(err, instance) { if (err) return done(err); - observersCalled.should.eql(['access', 'before save']); + hookMonitor.names.should.eql(['access', 'before save']); done(); }); }); @@ -2802,8 +2801,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); it('triggers hooks only once', function(done) { - TestModel.observe('access', pushNameAndNext('access')); - TestModel.observe('after delete', pushNameAndNext('after delete')); + monitorHookExecution(); TestModel.observe('access', function(ctx, next) { ctx.query = { where: { id: { neq: existingInstance.id } } }; next(); @@ -2811,7 +2809,7 @@ module.exports = function(dataSource, should, connectorCapabilities) { existingInstance.delete(function(err) { if (err) return done(err); - observersCalled.should.eql(['access', 'after delete']); + hookMonitor.names.should.eql(['access', 'before delete', 'after delete']); done(); }); }); @@ -2973,13 +2971,6 @@ module.exports = function(dataSource, should, connectorCapabilities) { }); - function pushNameAndNext(name) { - return function(context, next) { - observersCalled.push(name); - next(); - }; - } - function nextWithError(err) { return function(context, next) { next(err); @@ -3010,13 +3001,8 @@ module.exports = function(dataSource, should, connectorCapabilities) { TestModel.findOne({ where: { id: id } }, { notify: false }, cb); } - function monitorHookExecution() { - triggered = []; - TestModel._notify = TestModel.notifyObserversOf; - TestModel.notifyObserversOf = function(operation, context, callback) { - triggered.push(operation); - this._notify.apply(this, arguments); - }; + function monitorHookExecution(hookNames) { + hookMonitor.install(TestModel, hookNames); } });