diff --git a/src/batch.js b/src/batch.js index 1a425e1a21..329ae624d5 100644 --- a/src/batch.js +++ b/src/batch.js @@ -16,7 +16,7 @@ var async = require('async'), options = {}; } - callback = typeof callback === 'function' ? callback : function(){}; + callback = typeof callback === 'function' ? callback : function() {}; options = options || {}; if (typeof process !== 'function') { @@ -63,4 +63,52 @@ var async = require('async'), ); }; + Batch.processArray = function(array, process, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + callback = typeof callback === 'function' ? callback : function() {}; + options = options || {}; + + if (!Array.isArray(array) || !array.length) { + return callback(); + } + if (typeof process !== 'function') { + return callback(new Error('[[error:process-not-a-function]]')); + } + + var batch = options.batch || DEFAULT_BATCH_SIZE; + var start = 0; + var done = false; + + async.whilst( + function() { + return !done; + }, + function(next) { + var currentBatch = array.slice(start, start + batch); + if (!currentBatch.length) { + done = true; + return next(); + } + process(currentBatch, function(err) { + if (err) { + return next(err); + } + start = start + batch; + if (options.interval) { + setTimeout(next, options.interval); + } else { + next(); + } + }); + }, + function(err) { + callback(err); + } + ); + }; + }(exports)); diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index e7a1d0ba4e..c71c1b907c 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -10,6 +10,7 @@ var db = require('../database'); var posts = require('../posts'); var topics = require('../topics'); var privileges = require('../privileges'); +var batch = require('../batch'); module.exports = function(Categories) { @@ -182,7 +183,8 @@ module.exports = function(Categories) { }); } - Categories.moveRecentReplies = function(tid, oldCid, cid) { + Categories.moveRecentReplies = function(tid, oldCid, cid, callback) { + callback = callback || function() {}; updatePostCount(tid, oldCid, cid); topics.getPids(tid, function(err, pids) { if (err) { @@ -193,47 +195,31 @@ module.exports = function(Categories) { return; } - var start = 0, - done = false, - batch = 50; - - async.whilst(function() { - return !done; - }, function(next) { - var movePids = pids.slice(start, start + batch); - if (!movePids.length) { - done = true; - return next(); - } - - posts.getPostsFields(movePids, ['timestamp'], function(err, postData) { - if (err) { - return next(err); + batch.processArray(pids, function(pids, next) { + async.waterfall([ + function(next) { + posts.getPostsFields(pids, ['timestamp'], next); + }, + function(postData, next) { + var timestamps = postData.map(function(post) { + return post && post.timestamp; + }); + + async.parallel([ + function(next) { + db.sortedSetRemove('cid:' + oldCid + ':pids', pids, next); + }, + function(next) { + db.sortedSetAdd('cid:' + cid + ':pids', timestamps, pids, next); + } + ], next); } - - var timestamps = postData.map(function(post) { - return post && post.timestamp; - }); - - async.parallel([ - function(next) { - db.sortedSetRemove('cid:' + oldCid + ':pids', movePids, next); - }, - function(next) { - db.sortedSetAdd('cid:' + cid + ':pids', timestamps, movePids, next); - } - ], function(err) { - if (err) { - return next(err); - } - start += batch; - next(); - }); - }); + ], next); }, function(err) { if (err) { winston.error(err.stack); } + callback(err); }); }); }; diff --git a/src/groups/delete.js b/src/groups/delete.js index 8c7b194d28..af79757e9c 100644 --- a/src/groups/delete.js +++ b/src/groups/delete.js @@ -16,6 +16,7 @@ module.exports = function(Groups) { return callback(); } var groupObj = groupsData[0]; + plugins.fireHook('action:group.destroy', groupObj); async.parallel([ @@ -40,7 +41,11 @@ module.exports = function(Groups) { }); } ], function(err) { - callback(err); + if (err) { + return callback(err); + } + Groups.resetCache(); + callback(); }); }); }; diff --git a/src/notifications.js b/src/notifications.js index e4df61f260..7c0056a533 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -11,6 +11,7 @@ var db = require('./database'); var User = require('./user'); var groups = require('./groups'); var meta = require('./meta'); +var batch = require('./batch'); var plugins = require('./plugins'); var utils = require('../public/src/utils'); @@ -168,36 +169,14 @@ var utils = require('../public/src/utils'); return callback(); } - var done = false; - var start = 0; - var batchSize = 50; - setTimeout(function() { - async.whilst( - function() { - return !done; - }, - function(next) { - var currentUids = uids.slice(start, start + batchSize); - if (!currentUids.length) { - done = true; - return next(); - } - pushToUids(currentUids, notification, function(err) { - if (err) { - return next(err); - } - start = start + batchSize; - - setTimeout(next, 1000); - }); - }, - function(err) { - if (err) { - winston.error(err.stack); - } + batch.processArray(uids, function(uids, next) { + pushToUids(uids, notification, next); + }, {interval: 1000}, function(err) { + if (err) { + winston.error(err.stack); } - ); + }); }, 1000); callback(); diff --git a/test/categories.js b/test/categories.js index 9e762f5a90..f9ce55f916 100644 --- a/test/categories.js +++ b/test/categories.js @@ -1,19 +1,30 @@ 'use strict'; -/*global require, process, after*/ +/*global require, after, before*/ -var winston = require('winston'); -process.on('uncaughtException', function (err) { - winston.error('Encountered error while running test suite: ' + err.message); -}); - -var assert = require('assert'), - db = require('./mocks/databasemock'); +var async = require('async'); +var assert = require('assert'); +var db = require('./mocks/databasemock'); var Categories = require('../src/categories'); +var Topics = require('../src/topics'); +var User = require('../src/user'); describe('Categories', function() { var categoryObj; + var posterUid; + + before(function(done) { + User.create({username: 'poster'}, function(err, _posterUid) { + if (err) { + return done(err); + } + + posterUid = _posterUid; + + done(); + }); + }); describe('.create', function() { it('should create a new category', function(done) { @@ -115,6 +126,53 @@ describe('Categories', function() { }); }); + describe('Categories.moveRecentReplies', function() { + var moveCid; + var moveTid; + before(function(done) { + async.parallel({ + category: function(next) { + Categories.create({ + name: 'Test Category 2', + description: 'Test category created by testing script' + }, next); + }, + topic: function(next) { + Topics.post({ + uid: posterUid, + cid: categoryObj.cid, + title: 'Test Topic Title', + content: 'The content of test topic' + }, next); + } + }, function(err, results) { + if (err) { + return done(err); + } + moveCid = results.category.cid; + moveTid = results.topic.topicData.tid; + Topics.reply({uid: posterUid, content: 'test post', tid: moveTid}, function(err) { + done(err); + }); + }); + }); + + it('should move posts from one category to another', function(done) { + Categories.moveRecentReplies(moveTid, categoryObj.cid, moveCid, function(err) { + assert.ifError(err); + db.getSortedSetRange('cid:' + categoryObj.cid + ':pids', 0, -1, function(err, pids) { + assert.ifError(err); + assert.equal(pids.length, 0); + db.getSortedSetRange('cid:' + moveCid + ':pids', 0, -1, function(err, pids) { + assert.ifError(err); + assert.equal(pids.length, 2); + done(); + }); + }); + }); + }); + }); + after(function(done) { db.flushdb(done); }); diff --git a/test/groups.js b/test/groups.js index 43b1857b99..a28e84f14e 100644 --- a/test/groups.js +++ b/test/groups.js @@ -1,15 +1,16 @@ 'use strict'; /*global require, before, after*/ -var assert = require('assert'), - async = require('async'), +var assert = require('assert'); +var async = require('async'); - db = require('./mocks/databasemock'), - Groups = require('../src/groups'), - User = require('../src/user'); +var db = require('./mocks/databasemock'); +var Groups = require('../src/groups'); +var User = require('../src/user'); describe('Groups', function() { before(function(done) { + Groups.resetCache(); async.parallel([ function(next) { // Create a group to play around with @@ -35,8 +36,7 @@ describe('Groups', function() { describe('.list()', function() { it('should list the groups present', function(done) { Groups.getGroupsFromSet('groups:createtime', 0, 0, -1, function(err, groups) { - if (err) return done(err); - + assert.ifError(err); assert.equal(groups.length, 3); done(); }); diff --git a/test/notifications.js b/test/notifications.js new file mode 100644 index 0000000000..923087fce5 --- /dev/null +++ b/test/notifications.js @@ -0,0 +1,102 @@ +'use strict'; +/*global require, after, before*/ + + +var assert = require('assert'); + +var db = require('./mocks/databasemock'); +var user = require('../src/user'); +var notifications = require('../src/notifications'); + +describe('Notifications', function() { + var uid; + var notification; + + before(function(done) { + user.create({username: 'poster'}, function(err, _uid) { + if (err) { + return done(err); + } + + uid = _uid; + done(); + }); + }); + + it('should create a notification', function(done) { + notifications.create({ + bodyShort: 'bodyShort', + nid: 'notification_id' + }, function(err, _notification) { + notification = _notification; + assert.ifError(err); + assert(notification); + db.exists('notifications:' + notification.nid, function(err, exists) { + assert.ifError(err); + assert(exists); + db.isSortedSetMember('notifications', notification.nid, function(err, isMember) { + assert.ifError(err); + assert(isMember); + done(); + }); + }); + }); + }); + + it('should get notifications', function(done) { + notifications.getMultiple([notification.nid], function(err, notificationsData) { + assert.ifError(err); + assert(Array.isArray(notificationsData)); + assert(notificationsData[0]); + assert.equal(notification.nid, notificationsData[0].nid); + done(); + }); + }); + + it('should push a notification to uid', function(done) { + notifications.push(notification, [uid], function(err) { + assert.ifError(err); + setTimeout(function() { + db.isSortedSetMember('uid:' + uid + ':notifications:unread', notification.nid, function(err, isMember) { + assert.ifError(err); + assert(isMember); + done(); + }); + }, 2000); + }); + }); + + it('should mark a notification read', function(done) { + notifications.markRead(notification.nid, uid, function(err) { + assert.ifError(err); + db.isSortedSetMember('uid:' + uid + ':notifications:unread', notification.nid, function(err, isMember) { + assert.ifError(err); + assert.equal(isMember, false); + db.isSortedSetMember('uid:' + uid + ':notifications:read', notification.nid, function(err, isMember) { + assert.ifError(err); + assert.equal(isMember, true); + done(); + }); + }); + }); + }); + + it('should mark a notification unread', function(done) { + notifications.markUnread(notification.nid, uid, function(err) { + assert.ifError(err); + db.isSortedSetMember('uid:' + uid + ':notifications:unread', notification.nid, function(err, isMember) { + assert.ifError(err); + assert.equal(isMember, true); + db.isSortedSetMember('uid:' + uid + ':notifications:read', notification.nid, function(err, isMember) { + assert.ifError(err); + assert.equal(isMember, false); + done(); + }); + }); + }); + }); + + after(function(done) { + db.flushdb(done); + }); +});