UNPKG

12.6 kBJavaScriptView Raw
1'use strict';
2
3var assert = require('chai').assert;
4var Promise = require('bluebird');
5var Redlock = require('./redlock');
6
7test('https://www.npmjs.com/package/redis', [require('redis').createClient()]);
8test('https://www.npmjs.com/package/ioredis', [new (require('ioredis'))()]);
9
10/* istanbul ignore next */
11function test(name, clients){
12 var redlock = new Redlock(clients, {
13 retryCount: 2,
14 retryDelay: 150
15 });
16
17 var resource = 'Redlock:test:resource';
18 var error = 'Redlock:test:error';
19
20 describe('Redlock: ' + name, function(){
21
22 before(function(done) {
23 var err;
24 var l = clients.length; function cb(e){ if(e) err = e; l--; if(l === 0) done(err); }
25 for (var i = clients.length - 1; i >= 0; i--) {
26 clients[i].sadd(error, 'having a set here should cause a failure', cb);
27 }
28 });
29
30 it('should throw an error if not passed any clients', function(){
31 assert.throws(function(){
32 new Redlock([], {
33 retryCount: 2,
34 retryDelay: 150
35 });
36 });
37 });
38
39 it('emits a clientError event when a client error occurs', function(done){
40 var emitted = 0;
41 function test(err) {
42 assert.isNotNull(err);
43 emitted++;
44 }
45 redlock.on('clientError', test);
46 redlock.lock(error, 200, function(err, lock){
47 redlock.removeListener('clientError', test);
48 assert.isNotNull(err);
49 assert.equal(emitted, 3);
50 done();
51 });
52 });
53
54 describe('callbacks', function(){
55 before(function(done) {
56 var err;
57 var l = clients.length; function cb(e){ if(e) err = e; l--; if(l === 0) done(err); }
58 for (var i = clients.length - 1; i >= 0; i--) {
59 clients[i].del(resource, cb);
60 }
61 });
62
63 var one;
64 it('should lock a resource', function(done) {
65 redlock.lock(resource, 200, function(err, lock){
66 if(err) throw err;
67 assert.isObject(lock);
68 assert.isAbove(lock.expiration, Date.now()-1);
69 one = lock;
70 done();
71 });
72 });
73
74 var two;
75 var two_expiration;
76 it('should wait until a lock expires before issuing another lock', function(done) {
77 assert(one, 'Could not run because a required previous test failed.');
78 redlock.lock(resource, 800, function(err, lock){
79 if(err) throw err;
80 assert.isObject(lock);
81 assert.isAbove(lock.expiration, Date.now()-1);
82 assert.isAbove(Date.now()+1, one.expiration);
83 two = lock;
84 two_expiration = lock.expiration;
85 done();
86 });
87 });
88
89 it('should unlock a resource', function(done) {
90 assert(two, 'Could not run because a required previous test failed.');
91 two.unlock(done);
92 });
93
94 it('should unlock an already-unlocked resource', function(done) {
95 assert(two, 'Could not run because a required previous test failed.');
96 two.unlock(done);
97 });
98
99 it('should error when unable to fully release a resource', function(done) {
100 assert(two, 'Could not run because a required previous test failed.');
101 var failingTwo = Object.create(two);
102 failingTwo.resource = error;
103 failingTwo.unlock(function(err) {
104 assert.isNotNull(err);
105 done();
106 });
107 });
108
109 it('should fail to extend a lock on an already-unlocked resource', function(done) {
110 assert(two, 'Could not run because a required previous test failed.');
111 two.extend(200, function(err, lock){
112 assert.isNotNull(err);
113 assert.instanceOf(err, Redlock.LockError);
114 done();
115 });
116 });
117
118 var three;
119 it('should issue another lock immediately after a resource is unlocked', function(done) {
120 assert(two_expiration, 'Could not run because a required previous test failed.');
121 redlock.lock(resource, 800, function(err, lock){
122 if(err) throw err;
123 assert.isObject(lock);
124 assert.isAbove(lock.expiration, Date.now()-1);
125 assert.isBelow(Date.now()-1, two_expiration);
126 three = lock;
127 done();
128 });
129 });
130
131 var four;
132 it('should extend an unexpired lock', function(done) {
133 assert(three, 'Could not run because a required previous test failed.');
134 three.extend(800, function(err, lock){
135 if(err) throw err;
136 assert.isObject(lock);
137 assert.isAbove(lock.expiration, Date.now()-1);
138 assert.isAbove(lock.expiration, three.expiration-1);
139 assert.equal(three, lock);
140 four = lock;
141 done();
142 });
143 });
144
145 it('should fail after the maximum retry count is exceeded', function(done) {
146 assert(four, 'Could not run because a required previous test failed.');
147 redlock.lock(resource, 200, function(err, lock){
148 assert.isNotNull(err);
149 assert.instanceOf(err, Redlock.LockError);
150 done();
151 });
152 });
153
154 it('should fail to extend an expired lock', function(done) {
155 assert(four, 'Could not run because a required previous test failed.');
156 setTimeout(function(){
157 three.extend(800, function(err, lock){
158 assert.isNotNull(err);
159 assert.instanceOf(err, Redlock.LockError);
160 done();
161 });
162 }, four.expiration - Date.now() + 100);
163 });
164
165 it('should issue another lock immediately after a resource is expired', function(done) {
166 assert(four, 'Could not run because a required previous test failed.');
167 redlock.lock(resource, 800, function(err, lock){
168 if(err) throw err;
169 assert.isObject(lock);
170 assert.isAbove(lock.expiration, Date.now()-1);
171 done();
172 });
173 });
174
175 after(function(done) {
176 var err;
177 var l = clients.length; function cb(e){ if(e) err = e; l--; if(l === 0) done(err); }
178 for (var i = clients.length - 1; i >= 0; i--) {
179 clients[i].del(resource, cb);
180 }
181 });
182 });
183
184 describe('promises', function(){
185 before(function(done) {
186 var err;
187 var l = clients.length; function cb(e){ if(e) err = e; l--; if(l === 0) done(err); }
188 for (var i = clients.length - 1; i >= 0; i--) {
189 clients[i].del(resource, cb);
190 }
191 });
192
193 var one;
194 it('should lock a resource', function(done) {
195 redlock.lock(resource, 200)
196 .done(function(lock){
197 assert.isObject(lock);
198 assert.isAbove(lock.expiration, Date.now()-1);
199 one = lock;
200 done();
201 }, done);
202 });
203
204 var two;
205 var two_expiration;
206 it('should wait until a lock expires before issuing another lock', function(done) {
207 assert(one, 'Could not run because a required previous test failed.');
208 redlock.lock(resource, 800)
209 .done(function(lock){
210 assert.isObject(lock);
211 assert.isAbove(lock.expiration, Date.now()-1);
212 assert.isAbove(Date.now()+1, one.expiration);
213 two = lock;
214 two_expiration = lock.expiration;
215 done();
216 }, done);
217 });
218
219 it('should unlock a resource', function(done) {
220 assert(two, 'Could not run because a required previous test failed.');
221 two.unlock().done(done, done);
222 });
223
224 it('should unlock an already-unlocked resource', function(done) {
225 assert(two, 'Could not run because a required previous test failed.');
226 two.unlock().done(done, done);
227 });
228
229 it('should error when unable to fully release a resource', function(done) {
230 assert(two, 'Could not run because a required previous test failed.');
231 var failingTwo = Object.create(two);
232 failingTwo.resource = error;
233 failingTwo.unlock().done(done, function(err) {
234 assert.isNotNull(err);
235 done();
236 });
237 });
238
239 it('should fail to extend a lock on an already-unlocked resource', function(done) {
240 assert(two, 'Could not run because a required previous test failed.');
241 two.extend(200)
242 .done(function(){
243 done(new Error('Should have failed with a LockError'));
244 }, function(err){
245 assert.instanceOf(err, Redlock.LockError);
246 done();
247 });
248 });
249
250 var three;
251 it('should issue another lock immediately after a resource is unlocked', function(done) {
252 assert(two_expiration, 'Could not run because a required previous test failed.');
253 redlock.lock(resource, 800)
254 .done(function(lock){
255 assert.isObject(lock);
256 assert.isAbove(lock.expiration, Date.now()-1);
257 assert.isBelow(Date.now()-1, two_expiration);
258 three = lock;
259 done();
260 }, done);
261 });
262
263 var four;
264 it('should extend an unexpired lock', function(done) {
265 assert(three, 'Could not run because a required previous test failed.');
266 three.extend(800)
267 .done(function(lock){
268 assert.isObject(lock);
269 assert.isAbove(lock.expiration, Date.now()-1);
270 assert.isAbove(lock.expiration, three.expiration-1);
271 assert.equal(three, lock);
272 four = lock;
273 done();
274 }, done);
275 });
276
277 it('should fail after the maximum retry count is exceeded', function(done) {
278 assert(four, 'Could not run because a required previous test failed.');
279 redlock.lock(resource, 200)
280 .done(function(){
281 done(new Error('Should have failed with a LockError'));
282 }, function(err){
283 assert.instanceOf(err, Redlock.LockError);
284 done();
285 });
286 });
287
288 it('should fail to extend an expired lock', function(done) {
289 assert(four, 'Could not run because a required previous test failed.');
290 setTimeout(function(){
291 three.extend(800)
292 .done(function(){
293 done(new Error('Should have failed with a LockError'));
294 }, function(err){
295 assert.instanceOf(err, Redlock.LockError);
296 done();
297 });
298 }, four.expiration - Date.now() + 100);
299 });
300
301 after(function(done) {
302 var err;
303 var l = clients.length; function cb(e){ if(e) err = e; l--; if(l === 0) done(err); }
304 for (var i = clients.length - 1; i >= 0; i--) {
305 clients[i].del(resource, cb);
306 }
307 });
308 });
309
310 describe('disposer', function(){
311 before(function(done) {
312 var err;
313 var l = clients.length; function cb(e){ if(e) err = e; l--; if(l === 0) done(err); }
314 for (var i = clients.length - 1; i >= 0; i--) {
315 clients[i].del(resource, cb);
316 }
317 });
318
319 var one;
320 var one_expiration;
321 it('should automatically release a lock after the using block', function(done) {
322 Promise.using(
323 redlock.disposer(resource, 200),
324 function(lock){
325 assert.isObject(lock);
326 assert.isAbove(lock.expiration, Date.now()-1);
327 one = lock;
328 one_expiration = lock.expiration;
329 }
330 ).done(done, done);
331 });
332
333 var two;
334 var two_expiration;
335 it('should issue another lock immediately after a resource is unlocked', function(done) {
336 assert(one_expiration, 'Could not run because a required previous test failed.');
337 Promise.using(
338 redlock.disposer(resource, 800),
339 function(lock){
340 assert.isObject(lock);
341 assert.isAbove(lock.expiration, Date.now()-1);
342 assert.isBelow(Date.now()-1, one_expiration);
343 two = lock;
344 two_expiration = lock.expiration;
345 }
346 ).done(done, done);
347 });
348
349 it('should call unlockErrorHandler when unable to fully release a resource', function(done) {
350 assert(two, 'Could not run because a required previous test failed.');
351 var errs = 0;
352 var lock;
353 Promise.using(
354 redlock.disposer(resource, 800, function(err) {
355 errs++;
356 }),
357 function(l){
358 lock = l;
359 lock.resource = error;
360 }
361 ).done(function() {
362 assert.equal(errs, 1);
363 lock.resource = resource;
364 lock.unlock().done(done, done);
365 }, done);
366 });
367
368 var three_original, three_extended;
369 var three_original_expiration;
370 var three_extended_expiration;
371 it('should automatically release an extended lock', function(done) {
372 assert(two_expiration, 'Could not run because a required previous test failed.');
373 Promise.using(
374 redlock.disposer(resource, 200),
375 function(lock){
376 assert.isObject(lock);
377 assert.isAbove(lock.expiration, Date.now()-1);
378 assert.isBelow(Date.now()-1, two_expiration);
379 three_original = lock;
380 three_original_expiration = lock.expiration;
381
382 return Promise.delay(100)
383 .then(function(){ return lock.extend(200); })
384 .then(function(extended) {
385 assert.isObject(extended);
386 assert.isAbove(extended.expiration, Date.now()-1);
387 assert.isBelow(Date.now()-1, three_original_expiration);
388 assert.isAbove(extended.expiration, three_original_expiration);
389 assert.equal(extended, lock);
390 three_extended = extended;
391 three_extended_expiration = extended.expiration;
392 });
393 }
394 )
395 .then(function(){
396 assert.equal(three_original.expiration, 0);
397 assert.equal(three_extended.expiration, 0);
398 }).done(done, done);
399 });
400
401 after(function(done) {
402 var err;
403 var l = clients.length; function cb(e){ if(e) err = e; l--; if(l === 0) done(err); }
404 for (var i = clients.length - 1; i >= 0; i--) {
405 clients[i].del(resource, cb);
406 }
407 });
408 });
409
410 });
411}