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