UNPKG

11.8 kBJavaScriptView Raw
1// Copyright IBM Corp. 2013,2019. All Rights Reserved.
2// Node module: loopback-datasource-juggler
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6'use strict';
7
8// This test written in mocha+should.js
9/* global getSchema:false */
10const should = require('./init.js');
11
12const j = require('../'),
13 Schema = j.Schema,
14 AbstractClass = j.AbstractClass,
15 Hookable = j.Hookable;
16
17let db, User;
18
19describe('hooks', function() {
20 before(function(done) {
21 db = getSchema();
22
23 User = db.define('User', {
24 email: {type: String, index: true},
25 name: String,
26 password: String,
27 state: String,
28 });
29
30 db.automigrate('User', done);
31 });
32
33 describe('initialize', function() {
34 afterEach(function() {
35 User.afterInitialize = null;
36 });
37
38 it('should be triggered on new', function(done) {
39 User.afterInitialize = function() {
40 done();
41 };
42 new User;
43 });
44
45 it('should be triggered on create', function(done) {
46 let user;
47 User.afterInitialize = function() {
48 if (this.name === 'Nickolay') {
49 this.name += ' Rozental';
50 }
51 };
52 User.create({name: 'Nickolay'}, function(err, u) {
53 u.id.should.be.ok;
54 u.name.should.equal('Nickolay Rozental');
55 done();
56 });
57 });
58 });
59
60 describe('create', function() {
61 afterEach(removeHooks('Create'));
62
63 it('should be triggered on create', function(done) {
64 addHooks('Create', done);
65 User.create();
66 });
67
68 it('should not be triggered on new', function() {
69 User.beforeCreate = function(next) {
70 should.fail('This should not be called');
71 next();
72 };
73 const u = new User;
74 });
75
76 it('should be triggered on new+save', function(done) {
77 addHooks('Create', done);
78 (new User).save();
79 });
80
81 it('afterCreate should not be triggered on failed create', function(done) {
82 const old = User.dataSource.connector.create;
83 User.dataSource.connector.create = function(modelName, id, cb) {
84 cb(new Error('error'));
85 };
86
87 User.afterCreate = function() {
88 throw new Error('shouldn\'t be called');
89 };
90 User.create(function(err, user) {
91 User.dataSource.connector.create = old;
92 done();
93 });
94 });
95
96 it('afterCreate should not be triggered on failed beforeCreate', function(done) {
97 User.beforeCreate = function(next, data) {
98 // Skip next()
99 next(new Error('fail in beforeCreate'));
100 };
101
102 const old = User.dataSource.connector.create;
103 User.dataSource.connector.create = function(modelName, id, cb) {
104 throw new Error('shouldn\'t be called');
105 };
106
107 User.afterCreate = function() {
108 throw new Error('shouldn\'t be called');
109 };
110 User.create(function(err, user) {
111 User.dataSource.connector.create = old;
112 done();
113 });
114 });
115 });
116
117 describe('save', function() {
118 afterEach(removeHooks('Save'));
119
120 it('should be triggered on create', function(done) {
121 addHooks('Save', done);
122 User.create();
123 });
124
125 it('should be triggered on new+save', function(done) {
126 addHooks('Save', done);
127 (new User).save();
128 });
129
130 it('should be triggered on updateAttributes', function(done) {
131 User.create(function(err, user) {
132 addHooks('Save', done);
133 user.updateAttributes({name: 'Anatoliy'});
134 });
135 });
136
137 it('should be triggered on save', function(done) {
138 User.create(function(err, user) {
139 addHooks('Save', done);
140 user.name = 'Hamburger';
141 user.save();
142 });
143 });
144
145 it('should save full object', function(done) {
146 User.create(function(err, user) {
147 User.beforeSave = function(next, data) {
148 data.should.have.keys('id', 'name', 'email',
149 'password', 'state');
150 done();
151 };
152 user.save();
153 });
154 });
155
156 it('should save actual modifications to database', function(done) {
157 User.beforeSave = function(next, data) {
158 data.password = 'hash';
159 next();
160 };
161 User.destroyAll(function() {
162 User.create({
163 email: 'james.bond@example.com',
164 password: '53cr3t',
165 }, function() {
166 User.findOne({
167 where: {email: 'james.bond@example.com'},
168 }, function(err, jb) {
169 jb.password.should.equal('hash');
170 done();
171 });
172 });
173 });
174 });
175
176 it('should save actual modifications on updateAttributes', function(done) {
177 User.beforeSave = function(next, data) {
178 data.password = 'hash';
179 next();
180 };
181 User.destroyAll(function() {
182 User.create({
183 email: 'james.bond@example.com',
184 }, function(err, u) {
185 u.updateAttribute('password', 'new password', function(e, u) {
186 should.not.exist(e);
187 should.exist(u);
188 u.password.should.equal('hash');
189 User.findOne({
190 where: {email: 'james.bond@example.com'},
191 }, function(err, jb) {
192 jb.password.should.equal('hash');
193 done();
194 });
195 });
196 });
197 });
198 });
199
200 it('beforeSave should be able to skip next', function(done) {
201 User.create(function(err, user) {
202 User.beforeSave = function(next, data) {
203 next(null, 'XYZ');
204 };
205 user.save(function(err, result) {
206 result.should.be.eql('XYZ');
207 done();
208 });
209 });
210 });
211 });
212
213 describe('update', function() {
214 afterEach(removeHooks('Update'));
215
216 it('should not be triggered on create', function() {
217 User.beforeUpdate = function(next) {
218 should.fail('This should not be called');
219 next();
220 };
221 User.create();
222 });
223
224 it('should not be triggered on new+save', function() {
225 User.beforeUpdate = function(next) {
226 should.fail('This should not be called');
227 next();
228 };
229 (new User).save();
230 });
231
232 it('should be triggered on updateAttributes', function(done) {
233 User.create(function(err, user) {
234 addHooks('Update', done);
235 user.updateAttributes({name: 'Anatoliy'});
236 });
237 });
238
239 it('should be triggered on save', function(done) {
240 User.create(function(err, user) {
241 addHooks('Update', done);
242 user.name = 'Hamburger';
243 user.save();
244 });
245 });
246
247 it('should update limited set of fields', function(done) {
248 User.create(function(err, user) {
249 User.beforeUpdate = function(next, data) {
250 data.should.have.keys('name', 'email');
251 done();
252 };
253 user.updateAttributes({name: 1, email: 2});
254 });
255 });
256
257 it('should not trigger after-hook on failed save', function(done) {
258 User.afterUpdate = function() {
259 should.fail('afterUpdate shouldn\'t be called');
260 };
261 User.create(function(err, user) {
262 const save = User.dataSource.connector.save;
263 User.dataSource.connector.save = function(modelName, id, cb) {
264 User.dataSource.connector.save = save;
265 cb(new Error('Error'));
266 };
267
268 user.save(function(err) {
269 done();
270 });
271 });
272 });
273 });
274
275 describe('destroy', function() {
276 afterEach(removeHooks('Destroy'));
277
278 it('should be triggered on destroy', function(done) {
279 let hook = 'not called';
280 User.beforeDestroy = function(next) {
281 hook = 'called';
282 next();
283 };
284 User.afterDestroy = function(next) {
285 hook.should.eql('called');
286 next();
287 };
288 User.create(function(err, user) {
289 user.destroy(done);
290 });
291 });
292
293 it('should not trigger after-hook on failed destroy', function(done) {
294 const destroy = User.dataSource.connector.destroy;
295 User.dataSource.connector.destroy = function(modelName, id, cb) {
296 cb(new Error('error'));
297 };
298 User.afterDestroy = function() {
299 should.fail('afterDestroy shouldn\'t be called');
300 };
301 User.create(function(err, user) {
302 user.destroy(function(err) {
303 User.dataSource.connector.destroy = destroy;
304 done();
305 });
306 });
307 });
308 });
309
310 describe('lifecycle', function() {
311 let life = [], user;
312 before(function(done) {
313 User.beforeSave = function(d) {
314 life.push('beforeSave');
315 d();
316 };
317 User.beforeCreate = function(d) {
318 life.push('beforeCreate');
319 d();
320 };
321 User.beforeUpdate = function(d) {
322 life.push('beforeUpdate');
323 d();
324 };
325 User.beforeDestroy = function(d) {
326 life.push('beforeDestroy');
327 d();
328 };
329 User.beforeValidate = function(d) {
330 life.push('beforeValidate');
331 d();
332 };
333 User.afterInitialize = function() {
334 life.push('afterInitialize');
335 };
336 User.afterSave = function(d) {
337 life.push('afterSave');
338 d();
339 };
340 User.afterCreate = function(d) {
341 life.push('afterCreate');
342 d();
343 };
344 User.afterUpdate = function(d) {
345 life.push('afterUpdate');
346 d();
347 };
348 User.afterDestroy = function(d) {
349 life.push('afterDestroy');
350 d();
351 };
352 User.afterValidate = function(d) {
353 life.push('afterValidate');
354 d();
355 };
356 User.create(function(e, u) {
357 user = u;
358 life = [];
359 done();
360 });
361 });
362 beforeEach(function() {
363 life = [];
364 });
365
366 it('should describe create sequence', function(done) {
367 User.create(function() {
368 life.should.eql([
369 'afterInitialize',
370 'beforeValidate',
371 'afterValidate',
372 'beforeCreate',
373 'beforeSave',
374 'afterSave',
375 'afterCreate',
376 ]);
377 done();
378 });
379 });
380
381 it('should describe new+save sequence', function(done) {
382 const u = new User;
383 u.save(function() {
384 life.should.eql([
385 'afterInitialize',
386 'beforeValidate',
387 'afterValidate',
388 'beforeCreate',
389 'beforeSave',
390 'afterSave',
391 'afterCreate',
392 ]);
393 done();
394 });
395 });
396
397 it('should describe updateAttributes sequence', function(done) {
398 user.updateAttributes({name: 'Antony'}, function() {
399 life.should.eql([
400 'beforeValidate',
401 'afterValidate',
402 'beforeSave',
403 'beforeUpdate',
404 'afterUpdate',
405 'afterSave',
406 ]);
407 done();
408 });
409 });
410
411 it('should describe isValid sequence', function(done) {
412 should.not.exist(
413 user.constructor._validations,
414 'Expected user to have no validations, but she have',
415 );
416 user.isValid(function(valid) {
417 valid.should.be.true;
418 life.should.eql([
419 'beforeValidate',
420 'afterValidate',
421 ]);
422 done();
423 });
424 });
425
426 it('should describe destroy sequence', function(done) {
427 user.destroy(function() {
428 life.should.eql([
429 'beforeDestroy',
430 'afterDestroy',
431 ]);
432 done();
433 });
434 });
435 });
436});
437
438function addHooks(name, done) {
439 const random = String(Math.floor(Math.random() * 1000));
440 let called = false;
441 User['before' + name] = function(next, data) {
442 called = true;
443 data.email = random;
444 next();
445 };
446 User['after' + name] = function(next) {
447 (new Boolean(called)).should.equal(true);
448 this.should.have.property('email', random);
449 done();
450 };
451}
452
453function removeHooks(name) {
454 return function() {
455 User['after' + name] = null;
456 User['before' + name] = null;
457 };
458}