apostrophe
Version:
The Apostrophe Content Management System.
278 lines (259 loc) • 8.36 kB
JavaScript
var t = require('../test-lib/test.js');
var assert = require('assert');
var async = require('async');
var Promise = require('bluebird');
var _ = require('@sailshq/lodash');
var apos;
describe('Locks', function() {
this.timeout(t.timeout);
after(function(done) {
return t.destroy(apos, done);
});
it('should be a property of the apos object', function(done) {
this.timeout(t.timeout);
this.slow(2000);
apos = require('../index.js')({
root: module,
shortName: 'test',
modules: {
'apostrophe-express': {
port: 7900
},
// Make some subclasses of the locks module. NORMALLY A BAD IDEA. But
// we're doing it to deliberately force them to contend with each other,
// rather than just throwing an error saying "hey you have this lock now"
'apostrophe-locks-1': {
extend: 'apostrophe-locks',
alias: 'locks1'
},
'apostrophe-locks-2': {
extend: 'apostrophe-locks',
alias: 'locks2'
},
'apostrophe-locks-3': {
extend: 'apostrophe-locks',
alias: 'locks3'
}
},
afterInit: function(callback) {
assert(apos.modules['apostrophe-locks']);
assert(apos.modules['apostrophe-locks-1']);
assert(apos.modules['apostrophe-locks-2']);
assert(apos.modules['apostrophe-locks-3']);
// In tests this will be the name of the test file,
// so override that in order to get apostrophe to
// listen normally and not try to run a task. -Tom
apos.argv._ = [];
return callback(null);
},
afterListen: function(err) {
assert(!err);
done();
}
});
});
it('cleanup', function(done) {
apos.locks.db.remove({}, function(err) {
assert(!err);
done();
});
});
it('should allow a single lock without contention uneventfully', function(done) {
var locks = apos.modules['apostrophe-locks'];
return async.series([ lock, unlock ], function(err) {
assert(!err);
done();
});
function lock(callback) {
return locks.lock('test', callback);
}
function unlock(callback) {
return locks.unlock('test', callback);
}
});
it('should allow two differently-named locks uneventfully', function(done) {
var locks = apos.modules['apostrophe-locks'];
return async.series([ lock1, lock2, unlock1, unlock2 ], function(err) {
assert(!err);
done();
});
function lock1(callback) {
return locks.lock('test1', callback);
}
function unlock1(callback) {
return locks.unlock('test1', callback);
}
function lock2(callback) {
return locks.lock('test2', callback);
}
function unlock2(callback) {
return locks.unlock('test2', callback);
}
});
it('should flunk a second lock by the same module', function(done) {
var locks = apos.modules['apostrophe-locks'];
return async.series([ lock, lockAgain, unlock, unlockAgain ], function(err) {
assert(!err);
done();
});
function lock(callback) {
return locks.lock('test', callback);
}
function lockAgain(callback) {
return locks.lock('test', function(err) {
// SHOULD fail
assert(err);
return callback(null);
});
}
function unlock(callback) {
return locks.unlock('test', callback);
}
function unlockAgain(callback) {
return locks.unlock('test', function(err) {
// SHOULD fail
assert(err);
return callback(null);
});
}
});
it('four parallel lock calls via the different modules should all succeed but not simultaneously', function(done) {
var one = apos.modules['apostrophe-locks'];
var two = apos.modules['apostrophe-locks-1'];
var three = apos.modules['apostrophe-locks-2'];
var four = apos.modules['apostrophe-locks-3'];
var active = 0;
var successful = 0;
attempt(one);
attempt(two);
attempt(three);
attempt(four);
function attempt(locks) {
return locks.lock('test', function(err) {
assert(!err);
active++;
assert(active === 1);
setTimeout(release, 75 + Math.random() * 50);
});
function release() {
// We have to decrement this before we start the call to
// locks.unlock because otherwise the callback for one of our
// peers' insert attempts may succeed before the callback for
// remove, leading to a false positive for test failure. -Tom
active--;
return locks.unlock('test', function(err) {
assert(!err);
successful++;
if (successful === 4) {
done();
}
});
}
}
});
it('four parallel lock calls via the different modules should all succeed but not simultaneously, even when the idleTimeout is short', function(done) {
var one = apos.modules['apostrophe-locks'];
var two = apos.modules['apostrophe-locks-1'];
var three = apos.modules['apostrophe-locks-2'];
var four = apos.modules['apostrophe-locks-3'];
var active = 0;
var successful = 0;
attempt(one);
attempt(two);
attempt(three);
attempt(four);
function attempt(locks) {
return locks.lock('test', { idleTimeout: 50 }, function(err) {
assert(!err);
active++;
assert(active === 1);
setTimeout(release, 75 + Math.random() * 50);
});
function release() {
// We have to decrement this before we start the call to
// locks.unlock because otherwise the callback for one of our
// peers' insert attempts may succeed before the callback for
// remove, leading to a false positive for test failure. -Tom
active--;
return locks.unlock('test', function(err) {
assert(!err);
successful++;
if (successful === 4) {
done();
}
});
}
}
});
it('with promises: should flunk a second lock by the same module', function() {
var locks = apos.modules['apostrophe-locks'];
return Promise.try(function() {
return locks.lock('test');
}).then(function() {
return locks.lock('test')
.catch(function(err) {
// SHOULD fail
assert(err);
});
}).then(function() {
return locks.unlock('test');
}).then(function() {
return locks.unlock('test')
.catch(function(err) {
// SHOULD fail
assert(err);
});
});
});
it('withLock method should run a function inside a lock', function() {
var locks = apos.modules['apostrophe-locks'];
return locks.withLock('test-lock', function() {
return Promise.delay(50).then(function() {
return 'result';
});
}).then(function(result) {
assert(result === 'result');
});
});
it('withLock method should be able to run again (lock released)', function() {
var locks = apos.modules['apostrophe-locks'];
return locks.withLock('test-lock', function() {
return Promise.delay(50).then(function() {
return 'result';
});
}).then(function(result) {
assert(result === 'result');
});
});
it('withLock method should hold the lock (cannot relock within fn)', function() {
var locks = apos.modules['apostrophe-locks'];
return locks.withLock('test-lock', function() {
return Promise.delay(50).then(function() {
return locks.lock('test-lock').then(function() {
assert(false);
}).catch(function(e) {
assert(e);
});
});
});
});
it('callbacks: withLock method should run a function inside a lock', function(done) {
var locks = apos.modules['apostrophe-locks'];
return locks.withLock('test-lock', function(callback) {
return setTimeout(function() {
return callback(null, 'result');
}, 50);
}, function(err, result) {
assert(!err);
assert(result === 'result');
done();
});
});
it('all locks should be gone from the database', function() {
var locks = apos.modules['apostrophe-locks'];
return locks.db.find({}).toArray().then(function(locks) {
assert(!locks.length);
assert(!_.keys(locks.intervals).length);
});
});
});