1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict';
|
7 |
|
8 | const ModelBuilder = require('../').ModelBuilder;
|
9 | const should = require('./init');
|
10 |
|
11 | describe('async observer', function() {
|
12 | let TestModel;
|
13 | beforeEach(function defineTestModel() {
|
14 | const modelBuilder = new ModelBuilder();
|
15 | TestModel = modelBuilder.define('TestModel', {name: String});
|
16 | });
|
17 |
|
18 | it('calls registered async observers', function(done) {
|
19 | const notifications = [];
|
20 | TestModel.observe('before', pushAndNext(notifications, 'before'));
|
21 | TestModel.observe('after', pushAndNext(notifications, 'after'));
|
22 |
|
23 | TestModel.notifyObserversOf('before', {}, function(err) {
|
24 | if (err) return done(err);
|
25 | notifications.push('call');
|
26 | TestModel.notifyObserversOf('after', {}, function(err) {
|
27 | if (err) return done(err);
|
28 |
|
29 | notifications.should.eql(['before', 'call', 'after']);
|
30 | done();
|
31 | });
|
32 | });
|
33 | });
|
34 |
|
35 | it('allows multiple observers for the same operation', function(done) {
|
36 | const notifications = [];
|
37 | TestModel.observe('event', pushAndNext(notifications, 'one'));
|
38 | TestModel.observe('event', pushAndNext(notifications, 'two'));
|
39 |
|
40 | TestModel.notifyObserversOf('event', {}, function(err) {
|
41 | if (err) return done(err);
|
42 | notifications.should.eql(['one', 'two']);
|
43 | done();
|
44 | });
|
45 | });
|
46 |
|
47 | it('allows multiple operations to be notified in one call', function(done) {
|
48 | const notifications = [];
|
49 | TestModel.observe('event1', pushAndNext(notifications, 'one'));
|
50 | TestModel.observe('event2', pushAndNext(notifications, 'two'));
|
51 |
|
52 | TestModel.notifyObserversOf(['event1', 'event2'], {}, function(err) {
|
53 | if (err) return done(err);
|
54 | notifications.should.eql(['one', 'two']);
|
55 | done();
|
56 | });
|
57 | });
|
58 |
|
59 | it('inherits observers from base model', function(done) {
|
60 | const notifications = [];
|
61 | TestModel.observe('event', pushAndNext(notifications, 'base'));
|
62 |
|
63 | const Child = TestModel.extend('Child');
|
64 | Child.observe('event', pushAndNext(notifications, 'child'));
|
65 |
|
66 | Child.notifyObserversOf('event', {}, function(err) {
|
67 | if (err) return done(err);
|
68 | notifications.should.eql(['base', 'child']);
|
69 | done();
|
70 | });
|
71 | });
|
72 |
|
73 | it('allow multiple operations to be notified with base models', function(done) {
|
74 | const notifications = [];
|
75 | TestModel.observe('event1', pushAndNext(notifications, 'base1'));
|
76 | TestModel.observe('event2', pushAndNext(notifications, 'base2'));
|
77 |
|
78 | const Child = TestModel.extend('Child');
|
79 | Child.observe('event1', pushAndNext(notifications, 'child1'));
|
80 | Child.observe('event2', pushAndNext(notifications, 'child2'));
|
81 |
|
82 | Child.notifyObserversOf(['event1', 'event2'], {}, function(err) {
|
83 | if (err) return done(err);
|
84 | notifications.should.eql(['base1', 'child1', 'base2', 'child2']);
|
85 | done();
|
86 | });
|
87 | });
|
88 |
|
89 | it('does not modify observers in the base model', function(done) {
|
90 | const notifications = [];
|
91 | TestModel.observe('event', pushAndNext(notifications, 'base'));
|
92 |
|
93 | const Child = TestModel.extend('Child');
|
94 | Child.observe('event', pushAndNext(notifications, 'child'));
|
95 |
|
96 | TestModel.notifyObserversOf('event', {}, function(err) {
|
97 | if (err) return done(err);
|
98 | notifications.should.eql(['base']);
|
99 | done();
|
100 | });
|
101 | });
|
102 |
|
103 | it('always calls inherited observers', function(done) {
|
104 | const notifications = [];
|
105 | TestModel.observe('event', pushAndNext(notifications, 'base'));
|
106 |
|
107 | const Child = TestModel.extend('Child');
|
108 |
|
109 |
|
110 | Child.notifyObserversOf('event', {}, function(err) {
|
111 | if (err) return done(err);
|
112 | notifications.should.eql(['base']);
|
113 | done();
|
114 | });
|
115 | });
|
116 |
|
117 | it('can remove observers', function(done) {
|
118 | const notifications = [];
|
119 |
|
120 | function call(ctx, next) {
|
121 | notifications.push('call');
|
122 | process.nextTick(next);
|
123 | }
|
124 |
|
125 | TestModel.observe('event', call);
|
126 | TestModel.removeObserver('event', call);
|
127 |
|
128 | TestModel.notifyObserversOf('event', {}, function(err) {
|
129 | if (err) return done(err);
|
130 | notifications.should.eql([]);
|
131 | done();
|
132 | });
|
133 | });
|
134 |
|
135 | it('can clear all observers', function(done) {
|
136 | const notifications = [];
|
137 |
|
138 | function call(ctx, next) {
|
139 | notifications.push('call');
|
140 | process.nextTick(next);
|
141 | }
|
142 |
|
143 | TestModel.observe('event', call);
|
144 | TestModel.observe('event', call);
|
145 | TestModel.observe('event', call);
|
146 | TestModel.clearObservers('event');
|
147 |
|
148 | TestModel.notifyObserversOf('event', {}, function(err) {
|
149 | if (err) return done(err);
|
150 | notifications.should.eql([]);
|
151 | done();
|
152 | });
|
153 | });
|
154 |
|
155 | it('handles no observers', function(done) {
|
156 | TestModel.notifyObserversOf('no-observers', {}, function(err) {
|
157 |
|
158 | done(err);
|
159 | });
|
160 | });
|
161 |
|
162 | it('passes context to final callback', function(done) {
|
163 | const context = {};
|
164 | TestModel.notifyObserversOf('event', context, function(err, ctx) {
|
165 | (ctx || 'null').should.equal(context);
|
166 | done();
|
167 | });
|
168 | });
|
169 |
|
170 | describe('notifyObserversAround', function() {
|
171 | let notifications;
|
172 | beforeEach(function() {
|
173 | notifications = [];
|
174 | TestModel.observe('before execute',
|
175 | pushAndNext(notifications, 'before execute'));
|
176 | TestModel.observe('after execute',
|
177 | pushAndNext(notifications, 'after execute'));
|
178 | });
|
179 |
|
180 | it('should notify before/after observers', function(done) {
|
181 | const context = {};
|
182 |
|
183 | function work(done) {
|
184 | process.nextTick(function() {
|
185 | done(null, 1);
|
186 | });
|
187 | }
|
188 |
|
189 | TestModel.notifyObserversAround('execute', context, work,
|
190 | function(err, result) {
|
191 | notifications.should.eql(['before execute', 'after execute']);
|
192 | result.should.eql(1);
|
193 | done();
|
194 | });
|
195 | });
|
196 |
|
197 | it('should allow work with context', function(done) {
|
198 | const context = {};
|
199 |
|
200 | function work(context, done) {
|
201 | process.nextTick(function() {
|
202 | done(null, 1);
|
203 | });
|
204 | }
|
205 |
|
206 | TestModel.notifyObserversAround('execute', context, work,
|
207 | function(err, result) {
|
208 | notifications.should.eql(['before execute', 'after execute']);
|
209 | result.should.eql(1);
|
210 | done();
|
211 | });
|
212 | });
|
213 |
|
214 | it('should notify before/after observers with multiple results',
|
215 | function(done) {
|
216 | const context = {};
|
217 |
|
218 | function work(done) {
|
219 | process.nextTick(function() {
|
220 | done(null, 1, 2);
|
221 | });
|
222 | }
|
223 |
|
224 | TestModel.notifyObserversAround('execute', context, work,
|
225 | function(err, r1, r2) {
|
226 | r1.should.eql(1);
|
227 | r2.should.eql(2);
|
228 | notifications.should.eql(['before execute', 'after execute']);
|
229 | done();
|
230 | });
|
231 | });
|
232 |
|
233 | it('should allow observers to skip other ones',
|
234 | function(done) {
|
235 | TestModel.observe('before invoke',
|
236 | function(context, next) {
|
237 | notifications.push('before invoke');
|
238 | context.end(null, 0);
|
239 | });
|
240 | TestModel.observe('after invoke',
|
241 | pushAndNext(notifications, 'after invoke'));
|
242 |
|
243 | const context = {};
|
244 |
|
245 | function work(done) {
|
246 | process.nextTick(function() {
|
247 | done(null, 1, 2);
|
248 | });
|
249 | }
|
250 |
|
251 | TestModel.notifyObserversAround('invoke', context, work,
|
252 | function(err, r1) {
|
253 | r1.should.eql(0);
|
254 | notifications.should.eql(['before invoke']);
|
255 | done();
|
256 | });
|
257 | });
|
258 |
|
259 | it('should allow observers to tweak results',
|
260 | function(done) {
|
261 | TestModel.observe('after invoke',
|
262 | function(context, next) {
|
263 | notifications.push('after invoke');
|
264 | context.results = [3];
|
265 | next();
|
266 | });
|
267 |
|
268 | const context = {};
|
269 |
|
270 | function work(done) {
|
271 | process.nextTick(function() {
|
272 | done(null, 1, 2);
|
273 | });
|
274 | }
|
275 |
|
276 | TestModel.notifyObserversAround('invoke', context, work,
|
277 | function(err, r1) {
|
278 | r1.should.eql(3);
|
279 | notifications.should.eql(['after invoke']);
|
280 | done();
|
281 | });
|
282 | });
|
283 | });
|
284 |
|
285 | it('resolves promises returned by observers', function(done) {
|
286 | TestModel.observe('event', function(ctx) {
|
287 | return Promise.resolve('value-to-ignore');
|
288 | });
|
289 | TestModel.notifyObserversOf('event', {}, function(err, ctx) {
|
290 |
|
291 | done();
|
292 | });
|
293 | });
|
294 |
|
295 | it('handles rejected promise returned by an observer', function(done) {
|
296 | const testError = new Error('expected test error');
|
297 | TestModel.observe('event', function(ctx) {
|
298 | return Promise.reject(testError);
|
299 | });
|
300 | TestModel.notifyObserversOf('event', {}, function(err, ctx) {
|
301 | err.should.eql(testError);
|
302 | done();
|
303 | });
|
304 | });
|
305 |
|
306 | it('returns a promise when no callback is provided', function() {
|
307 | const context = {value: 'a-test-context'};
|
308 | const p = TestModel.notifyObserversOf('event', context);
|
309 | (p !== undefined).should.be.true;
|
310 | return p.then(function(result) {
|
311 | result.should.eql(context);
|
312 | });
|
313 | });
|
314 |
|
315 | it('returns a rejected promise when no callback is provided', function() {
|
316 | const testError = new Error('expected test error');
|
317 | TestModel.observe('event', function(ctx, next) { next(testError); });
|
318 | const p = TestModel.notifyObserversOf('event', context);
|
319 | return p.then(
|
320 | function(result) {
|
321 | throw new Error('The promise should have been rejected.');
|
322 | },
|
323 | function(err) {
|
324 | err.should.eql(testError);
|
325 | },
|
326 | );
|
327 | });
|
328 |
|
329 | it('should call after operation hook on error', function(done) {
|
330 | const context = {
|
331 | req: {},
|
332 | };
|
333 | const operationError = new Error('The operation failed without result');
|
334 | let callCount = 0;
|
335 |
|
336 | function fail(context, done) {
|
337 | process.nextTick(() => {
|
338 | done(operationError);
|
339 | });
|
340 | }
|
341 |
|
342 | TestModel.observe('after execute error', function(ctx, next) {
|
343 | callCount++;
|
344 | next();
|
345 | });
|
346 |
|
347 | TestModel.notifyObserversAround('execute', context, fail, (err, ctx) => {
|
348 | callCount.should.eql(1);
|
349 | err.message.should.eql(operationError.message);
|
350 | ctx.error.message.should.eql(operationError.message);
|
351 | done();
|
352 | });
|
353 | });
|
354 |
|
355 | it('should call after operation hook on error while overwriting error', function(done) {
|
356 | const context = {
|
357 | req: {},
|
358 | };
|
359 | const operationError = new Error('The operation failed without result');
|
360 | const overwriteError = new Error('Overwriting the original error');
|
361 | let callCount = 0;
|
362 |
|
363 | function fail(context, done) {
|
364 | process.nextTick(() => {
|
365 | done(operationError);
|
366 | });
|
367 | }
|
368 |
|
369 | TestModel.observe('after execute error', function(ctx, next) {
|
370 | callCount++;
|
371 | next(overwriteError);
|
372 | });
|
373 |
|
374 | TestModel.notifyObserversAround('execute', context, fail, (err, ctx) => {
|
375 | callCount.should.eql(1);
|
376 | err.message.should.eql(overwriteError.message);
|
377 | ctx.error.message.should.eql(operationError.message);
|
378 | done();
|
379 | });
|
380 | });
|
381 |
|
382 | it('should call after operation hook on error while allowing to change err', function(done) {
|
383 | const context = {
|
384 | req: {},
|
385 | };
|
386 | const operationError = new Error('The operation failed without result');
|
387 | let callCount = 0;
|
388 |
|
389 | function fail(context, done) {
|
390 | process.nextTick(() => {
|
391 | done(operationError);
|
392 | });
|
393 | }
|
394 |
|
395 | TestModel.observe('after execute error', function(ctx, next) {
|
396 | callCount++;
|
397 | const err = ctx.error;
|
398 | next(err, ctx);
|
399 | });
|
400 |
|
401 | TestModel.notifyObserversAround('execute', context, fail, (err, ctx) => {
|
402 | callCount.should.eql(1);
|
403 | err.message.should.eql(operationError.message);
|
404 | ctx.error.message.should.eql(operationError.message);
|
405 | done();
|
406 | });
|
407 | });
|
408 | });
|
409 |
|
410 | function pushAndNext(array, value) {
|
411 | return function(ctx, next) {
|
412 | array.push(value);
|
413 | process.nextTick(next);
|
414 | };
|
415 | }
|