1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict';
|
7 |
|
8 |
|
9 |
|
10 | const should = require('./init.js');
|
11 |
|
12 | const j = require('../'),
|
13 | Schema = j.Schema,
|
14 | AbstractClass = j.AbstractClass,
|
15 | Hookable = j.Hookable;
|
16 |
|
17 | let db, User;
|
18 |
|
19 | describe('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 |
|
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 |
|
438 | function 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 |
|
453 | function removeHooks(name) {
|
454 | return function() {
|
455 | User['after' + name] = null;
|
456 | User['before' + name] = null;
|
457 | };
|
458 | }
|