diff --git a/Gruntfile.js b/Gruntfile.js index 08baead7a8..be761a16cf 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -35,7 +35,6 @@ module.exports = function (grunt) { incomplete.push(compiling); } - // @psychobunny, re: #5211, instead of this, just call `node app --build js` or `node app --build css,tpl` updateArgs.push('--build'); updateArgs.push(incomplete.join(',')); diff --git a/package.json b/package.json index 7bcd0ce3da..bf9fb0049e 100644 --- a/package.json +++ b/package.json @@ -81,9 +81,9 @@ "semver": "^5.1.0", "serve-favicon": "^2.1.5", "sitemap": "^1.4.0", - "socket.io": "^1.4.8", - "socket.io-client": "^1.4.0", - "socket.io-redis": "1.1.1", + "socket.io": "1.7.1", + "socket.io-client": "1.7.1", + "socket.io-redis": "2.0.0", "socketio-wildcard": "~0.3.0", "string": "^3.0.0", "templates.js": "0.3.4", diff --git a/public/src/client/chats/messages.js b/public/src/client/chats/messages.js index 3bce27f416..1ac8e2a2ff 100644 --- a/public/src/client/chats/messages.js +++ b/public/src/client/chats/messages.js @@ -27,6 +27,7 @@ define('forum/chats/messages', ['components', 'sounds', 'translator'], function message: msg }, function (err) { if (err) { + inputEl.val(msg); if (err.message === '[[error:email-not-confirmed-chat]]') { return app.showEmailConfirmWarning(err); } @@ -42,6 +43,8 @@ define('forum/chats/messages', ['components', 'sounds', 'translator'], function message: msg }, function (err) { if (err) { + inputEl.val(msg); + inputEl.attr('data-mid', mid); return app.alertError(err.message); } }); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 2283695d39..d90dc210d4 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -258,7 +258,7 @@ define('chat', [ Chats.addScrollHandler(chatModal.attr('roomId'), data.uid, chatModal.find('.chat-content')); taskbar.push('chat', chatModal.attr('UUID'), { - title: data.users.length ? data.users[0].username : '', + title: data.roomName || (data.users.length ? data.users[0].username : ''), roomId: data.roomId, icon: 'fa-comment', state: '' diff --git a/src/categories/unread.js b/src/categories/unread.js index 3805e1e78f..3a1ba27277 100644 --- a/src/categories/unread.js +++ b/src/categories/unread.js @@ -33,6 +33,9 @@ module.exports = function (Categories) { }; Categories.markAsUnreadForAll = function (cid, callback) { + if (!parseInt(cid, 10)) { + return callback(); + } callback = callback || function () {}; db.delete('cid:' + cid + ':read_by_uid', callback); }; diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 20c2b87cbd..1224de5bcd 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -105,9 +105,9 @@ topicsController.get = function (req, res, callback) { } else if (!req.query.page) { var index; if (reverse) { - index = Math.max(0, postCount - (req.params.post_index || postCount)); + index = Math.max(0, postCount - (req.params.post_index || postCount) + 2); } else { - index = Math.max(0, req.params.post_index - 1) || 0; + index = Math.max(0, req.params.post_index) || 0; } currentPage = Math.max(1, Math.ceil(index / settings.postsPerPage)); diff --git a/src/groups/update.js b/src/groups/update.js index fa998bafe6..4dfe760a96 100644 --- a/src/groups/update.js +++ b/src/groups/update.js @@ -12,48 +12,57 @@ module.exports = function (Groups) { Groups.update = function (groupName, values, callback) { callback = callback || function () {}; - db.exists('group:' + groupName, function (err, exists) { - if (err || !exists) { - return callback(err || new Error('[[error:no-group]]')); - } - plugins.fireHook('filter:group.update', { - groupName: groupName, - values: values - }, function (err) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + db.exists('group:' + groupName, next); + }, + function (exists, next) { + if (!exists) { + return next(new Error('[[error:no-group]]')); } + plugins.fireHook('filter:group.update', { + groupName: groupName, + values: values + }, next); + }, + function (result, next) { + values = result.values; var payload = { description: values.description || '', icon: values.icon || '', labelColor: values.labelColor || '#000000' }; - + if (values.hasOwnProperty('userTitle')) { payload.userTitle = values.userTitle || ''; } - + if (values.hasOwnProperty('userTitleEnabled')) { payload.userTitleEnabled = values.userTitleEnabled ? '1' : '0'; } - + if (values.hasOwnProperty('hidden')) { payload.hidden = values.hidden ? '1' : '0'; } - + if (values.hasOwnProperty('private')) { payload.private = values.private ? '1' : '0'; } - + if (values.hasOwnProperty('disableJoinRequests')) { payload.disableJoinRequests = values.disableJoinRequests ? '1' : '0'; } - async.series([ async.apply(checkNameChange, groupName, values.name), - async.apply(updatePrivacy, groupName, values.private), + function (next) { + if (values.hasOwnProperty('private')) { + updatePrivacy(groupName, values.private, next); + } else { + next(); + } + }, function (next) { if (values.hasOwnProperty('hidden')) { updateVisibility(groupName, values.hidden, next); @@ -63,19 +72,16 @@ module.exports = function (Groups) { }, async.apply(db.setObject, 'group:' + groupName, payload), async.apply(renameGroup, groupName, values.name) - ], function (err) { - if (err) { - return callback(err); - } - - plugins.fireHook('action:group.update', { - name: groupName, - values: values - }); - callback(); + ], next); + }, + function (result, next) { + plugins.fireHook('action:group.update', { + name: groupName, + values: values }); - }); - }); + next(); + } + ], callback); }; function updateVisibility(groupName, hidden, callback) { @@ -118,35 +124,33 @@ module.exports = function (Groups) { }); } - function updatePrivacy(groupName, newValue, callback) { - if (!newValue) { - return callback(); - } + function updatePrivacy(groupName, isPrivate, callback) { + async.waterfall([ + function (next) { + Groups.getGroupFields(groupName, ['private'], next); + }, + function (currentValue, next) { + var currentlyPrivate = parseInt(currentValue.private, 10) === 1; + if (!currentlyPrivate || currentlyPrivate === isPrivate) { + return callback(); + } + db.getSetMembers('group:' + groupName + ':pending', next); + }, + function (uids, next) { + if (!uids.length) { + return callback(); + } + var now = Date.now(); + var scores = uids.map(function () { return now; }); - Groups.getGroupFields(groupName, ['private'], function (err, currentValue) { - if (err) { - return callback(err); - } - currentValue = currentValue.private === '1'; - - if (currentValue !== newValue && currentValue === true) { - // Group is now public, so all pending users are automatically considered members - db.getSetMembers('group:' + groupName + ':pending', function (err, uids) { - if (err) { return callback(err); } - else if (!uids) { return callback(); } // No pending users, we're good to go - - var now = Date.now(), - scores = uids.map(function () { return now; }); // There's probably a better way to initialise an Array of size x with the same value... - - winston.verbose('[groups.update] Group is now public, automatically adding ' + uids.length + ' new members, who were pending prior.'); - async.series([ - async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', scores, uids), - async.apply(db.delete, 'group:' + groupName + ':pending') - ], callback); - }); - } else { - callback(); + winston.verbose('[groups.update] Group is now public, automatically adding ' + uids.length + ' new members, who were pending prior.'); + async.series([ + async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', scores, uids), + async.apply(db.delete, 'group:' + groupName + ':pending') + ], next); } + ], function (err) { + callback(err); }); } diff --git a/src/meta/js.js b/src/meta/js.js index 72c9813a03..626fa0ecd8 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -16,7 +16,7 @@ module.exports = function (Meta) { scripts: { base: [ './node_modules/jquery/dist/jquery.js', - './node_modules/socket.io-client/socket.io.js', + './node_modules/socket.io-client/dist/socket.io.js', 'public/vendor/jquery/timeago/jquery.timeago.js', 'public/vendor/jquery/js/jquery.form.min.js', 'public/vendor/visibility/visibility.min.js', diff --git a/src/notifications.js b/src/notifications.js index ed2e98b7da..b99700be01 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -352,7 +352,9 @@ var utils = require('../public/src/utils'); function (next) { db.sortedSetAdd('uid:' + uid + ':notifications:read', datetimes, nids, next); } - ], callback); + ], function (err) { + callback(err); + }); }); }; diff --git a/src/plugins/load.js b/src/plugins/load.js index d5ddfa9dd2..2d2a377070 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -261,7 +261,7 @@ module.exports = function (Plugins) { } var pathToFolder = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages); - var defaultLang = pluginData.defaultLang.replace('_', '-').replace('@', '-x-'); + var defaultLang = (pluginData.defaultLang || 'en_GB').replace('_', '-').replace('@', '-x-'); utils.walk(pathToFolder, function (err, languages) { if (err) { @@ -346,9 +346,11 @@ module.exports = function (Plugins) { if (err) { return callback(err); } + var pluginData; + var packageData; try { - var pluginData = JSON.parse(results.plugin); - var packageData = JSON.parse(results.package); + pluginData = JSON.parse(results.plugin); + packageData = JSON.parse(results.package); pluginData.id = packageData.name; pluginData.name = packageData.name; @@ -356,16 +358,15 @@ module.exports = function (Plugins) { pluginData.version = packageData.version; pluginData.repository = packageData.repository; pluginData.nbbpm = packageData.nbbpm; - - callback(null, pluginData); } catch(err) { var pluginDir = pluginPath.split(path.sep); pluginDir = pluginDir[pluginDir.length - 1]; winston.error('[plugins/' + pluginDir + '] Error in plugin.json or package.json! ' + err.message); - callback(new Error('[[error:parse-error]]')); + return callback(new Error('[[error:parse-error]]')); } + callback(null, pluginData); }); }; }; diff --git a/src/sitemap.js b/src/sitemap.js index 9e52985c02..ec068d71d3 100644 --- a/src/sitemap.js +++ b/src/sitemap.js @@ -9,13 +9,14 @@ var categories = require('./categories'); var topics = require('./topics'); var privileges = require('./privileges'); var meta = require('./meta'); +var plugins = require('./plugins'); var utils = require('../public/src/utils'); var sitemap = { - maps: { - topics: [] - } - }; + maps: { + topics: [] + } +}; sitemap.render = function (callback) { var numTopics = parseInt(meta.config.sitemapTopics, 10) || 500; @@ -71,13 +72,18 @@ sitemap.getPages = function (callback) { priority: 0.4 }]; - sitemap.maps.pages = sm.createSitemap({ - hostname: nconf.get('url'), - cacheTime: 1000 * 60 * 60 * 24, // Cached for 24 hours - urls: urls - }); + plugins.fireHook('filter:sitemap.getPages', {urls: urls}, function (err, data) { + if (err) { + return callback(err); + } + sitemap.maps.pages = sm.createSitemap({ + hostname: nconf.get('url'), + cacheTime: 1000 * 60 * 60 * 24, // Cached for 24 hours + urls: data.urls + }); - sitemap.maps.pages.toXML(callback); + sitemap.maps.pages.toXML(callback); + }); }; sitemap.getCategories = function (callback) { diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 7cc43178d6..fe729a5c11 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -44,7 +44,7 @@ SocketPosts.reply = function (socket, data, callback) { callback(null, postData); - socket.emit('event:new_post', result); + websockets.in('uid_' + socket.uid).emit('event:new_post', result); user.updateOnlineUsers(socket.uid); diff --git a/src/socket.io/topics/unread.js b/src/socket.io/topics/unread.js index 029a0c9e74..39c6485a26 100644 --- a/src/socket.io/topics/unread.js +++ b/src/socket.io/topics/unread.js @@ -11,61 +11,67 @@ module.exports = function (SocketTopics) { if (!Array.isArray(tids) || !socket.uid) { return callback(new Error('[[error:invalid-data]]')); } - - topics.markAsRead(tids, socket.uid, function (err) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + topics.markAsRead(tids, socket.uid, next); + }, + function (hasMarked, next) { + if (hasMarked) { + topics.pushUnreadCount(socket.uid); + + topics.markTopicNotificationsRead(tids, socket.uid); + } + next(); } - - topics.pushUnreadCount(socket.uid); - - topics.markTopicNotificationsRead(tids, socket.uid); - - callback(); - }); + ], callback); }; SocketTopics.markTopicNotificationsRead = function (socket, tids, callback) { if (!Array.isArray(tids) || !socket.uid) { return callback(new Error('[[error:invalid-data]]')); } - topics.markTopicNotificationsRead(tids, socket.uid); + topics.markTopicNotificationsRead(tids, socket.uid, callback); }; SocketTopics.markAllRead = function (socket, data, callback) { - topics.markAllRead(socket.uid, function (err) { - if (err) { - return callback(err); + if (!socket.uid) { + return callback(new Error('[[error:invalid-uid]]')); + } + async.waterfall([ + function (next) { + topics.markAllRead(socket.uid, next); + }, + function (next) { + topics.pushUnreadCount(socket.uid); + next(); } - - topics.pushUnreadCount(socket.uid); - - callback(); - }); + ], callback); }; SocketTopics.markCategoryTopicsRead = function (socket, cid, callback) { - topics.getUnreadTids(cid, socket.uid, '', function (err, tids) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + topics.getUnreadTids(cid, socket.uid, '', next); + }, + function (tids, next) { + SocketTopics.markAsRead(socket, tids, next); } - - SocketTopics.markAsRead(socket, tids, callback); - }); + ], callback); }; SocketTopics.markUnread = function (socket, tid, callback) { if (!tid || !socket.uid) { return callback(new Error('[[error:invalid-data]]')); } - topics.markUnread(tid, socket.uid, function (err) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + topics.markUnread(tid, socket.uid, next); + }, + function (next) { + topics.pushUnreadCount(socket.uid); + next(); } - - topics.pushUnreadCount(socket.uid); - callback(); - }); + ], callback); }; SocketTopics.markAsUnreadForAll = function (socket, tids, callback) { @@ -77,42 +83,41 @@ module.exports = function (SocketTopics) { return callback(new Error('[[error:no-privileges]]')); } - user.isAdministrator(socket.uid, function (err, isAdmin) { - if (err) { - return callback(err); - } - - async.each(tids, function (tid, next) { - async.waterfall([ - function (next) { - topics.exists(tid, next); - }, - function (exists, next) { - if (!exists) { - return next(new Error('[[error:invalid-tid]]')); - } - topics.getTopicField(tid, 'cid', next); - }, - function (cid, next) { - user.isModerator(socket.uid, cid, next); - }, - function (isMod, next) { - if (!isAdmin && !isMod) { - return next(new Error('[[error:no-privileges]]')); + async.waterfall([ + function (next) { + user.isAdministrator(socket.uid, next); + }, + function (isAdmin, next) { + async.each(tids, function (tid, next) { + async.waterfall([ + function (next) { + topics.exists(tid, next); + }, + function (exists, next) { + if (!exists) { + return next(new Error('[[error:no-topic]]')); + } + topics.getTopicField(tid, 'cid', next); + }, + function (cid, next) { + user.isModerator(socket.uid, cid, next); + }, + function (isMod, next) { + if (!isAdmin && !isMod) { + return next(new Error('[[error:no-privileges]]')); + } + topics.markAsUnreadForAll(tid, next); + }, + function (next) { + topics.updateRecent(tid, Date.now(), next); } - topics.markAsUnreadForAll(tid, next); - }, - function (next) { - topics.updateRecent(tid, Date.now(), next); - } - ], next); - }, function (err) { - if (err) { - return callback(err); - } + ], next); + }, next); + }, + function (next) { topics.pushUnreadCount(socket.uid); - callback(); - }); - }); + next(); + } + ], callback); }; }; \ No newline at end of file diff --git a/src/topics/follow.js b/src/topics/follow.js index 6993920fc0..0667218588 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -74,7 +74,10 @@ module.exports = function (Topics) { function (next) { method2(tid, uid, next); }, - async.apply(plugins.fireHook, hook, {uid: uid, tid: tid}) + function (next) { + plugins.fireHook(hook, {uid: uid, tid: tid}); + next(); + } ], callback); } diff --git a/src/topics/unread.js b/src/topics/unread.js index c7cabb2881..e61ca46d59 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -2,7 +2,6 @@ 'use strict'; var async = require('async'); -var winston = require('winston'); var db = require('../database'); var user = require('../user'); @@ -277,9 +276,10 @@ module.exports = function (Topics) { ], callback); }; - Topics.markTopicNotificationsRead = function (tids, uid) { + Topics.markTopicNotificationsRead = function (tids, uid, callback) { + callback = callback || function () {}; if (!Array.isArray(tids) || !tids.length) { - return; + return callback(); } async.waterfall([ @@ -288,23 +288,23 @@ module.exports = function (Topics) { }, function (nids, next) { notifications.markReadMultiple(nids, uid, next); + }, + function (next) { + user.notifications.pushCount(uid); + next(); } - ], function (err) { - if (err) { - return winston.error(err); - } - user.notifications.pushCount(uid); - }); + ], callback); }; Topics.markCategoryUnreadForAll = function (tid, callback) { - Topics.getTopicField(tid, 'cid', function (err, cid) { - if(err) { - return callback(err); + async.waterfall([ + function (next) { + Topics.getTopicField(tid, 'cid', next); + }, + function (cid, next) { + categories.markAsUnreadForAll(cid, next); } - - categories.markAsUnreadForAll(cid, callback); - }); + ], callback); }; Topics.hasReadTopics = function (tids, uid, callback) { diff --git a/test/groups.js b/test/groups.js index 6a71933fb7..122645cc2b 100644 --- a/test/groups.js +++ b/test/groups.js @@ -504,6 +504,128 @@ describe('Groups', function () { }); + describe('admin socket methods', function () { + var socketGroups = require('../src/socket.io/admin/groups'); + + it('should fail to create group with invalid data', function (done) { + socketGroups.create({uid: adminUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should fail to create group if group name is privilege group', function (done) { + socketGroups.create({uid: adminUid}, {name: 'cid:1:privileges:read'}, function (err) { + assert.equal(err.message, '[[error:invalid-group-name]]'); + done(); + }); + }); + + it('should create a group', function (done) { + socketGroups.create({uid: adminUid}, {name: 'newgroup', description: 'group created by admin'}, function (err, groupData) { + assert.ifError(err); + assert.equal(groupData.name, 'newgroup'); + assert.equal(groupData.description, 'group created by admin'); + assert.equal(groupData.ownerUid, adminUid); + assert.equal(groupData.private, true); + assert.equal(groupData.memberCount, 1); + done(); + }); + }); + + it('should fail to join with invalid data', function (done) { + socketGroups.join({uid: adminUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should add user to group', function (done) { + socketGroups.join({uid: adminUid}, {uid: testUid, groupName: 'newgroup'}, function (err) { + assert.ifError(err); + Groups.isMember(testUid, 'newgroup', function (err, isMember) { + assert.ifError(err); + assert(isMember); + done(); + }); + }); + }); + + it('should fail to if user is already member', function (done) { + socketGroups.join({uid: adminUid}, {uid: testUid, groupName: 'newgroup'}, function (err) { + assert.equal(err.message, '[[error:group-already-member]]'); + done(); + }); + }); + + it('it should fail with invalid data', function (done) { + socketGroups.leave({uid: adminUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('it should fail if admin tries to remove self', function (done) { + socketGroups.leave({uid: adminUid}, {uid: adminUid, groupName: 'administrators'}, function (err) { + assert.equal(err.message, '[[error:cant-remove-self-as-admin]]'); + done(); + }); + }); + + it('should fail if user is not member', function (done) { + socketGroups.leave({uid: adminUid}, {uid: 3, groupName: 'newgroup'}, function (err) { + assert.equal(err.message, '[[error:group-not-member]]'); + done(); + }); + }); + + it('should remove user from group', function (done) { + socketGroups.leave({uid: adminUid}, {uid: testUid, groupName: 'newgroup'}, function (err) { + assert.ifError(err); + Groups.isMember(testUid, 'newgroup', function (err, isMember) { + assert.ifError(err); + assert(!isMember); + done(); + }); + }); + }); + + it('should fail with invalid data', function (done) { + socketGroups.update({uid: adminUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should update group', function (done) { + var data = { + groupName: 'newgroup', + values: { + name: 'renamedgroup', + description: 'cat group', + userTitle: 'cats', + userTitleEnabled: 1, + disableJoinRequests: 1, + hidden: 1, + private: 0 + } + }; + socketGroups.update({uid: adminUid}, data, function (err) { + assert.ifError(err); + Groups.get('renamedgroup', {}, function (err, groupData) { + assert.ifError(err); + assert.equal(groupData.name, 'renamedgroup'); + assert.equal(groupData.userTitle, 'cats'); + assert.equal(groupData.description, 'cat group'); + assert.equal(groupData.hidden, true); + assert.equal(groupData.disableJoinRequests, true); + assert.equal(groupData.private, false); + done(); + }); + }); + }); + }); + after(function (done) { db.emptydb(done); }); diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index b5b384a679..f1fcb89c5a 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -100,6 +100,9 @@ function (next) { meta.configs.init(next); }, + function (next) { + meta.dependencies.check(next); + }, function (next) { meta.config.postDelay = 0; meta.config.initialPostDelay = 0; @@ -107,6 +110,12 @@ enableDefaultPlugins(next); }, + function (next) { + meta.themes.set({ + type: 'local', + id: 'nodebb-theme-persona' + }, next); + }, function (next) { // nconf defaults, if not set in config if (!nconf.get('upload_path')) { diff --git a/test/topics.js b/test/topics.js index 4972257f5b..392296928a 100644 --- a/test/topics.js +++ b/test/topics.js @@ -683,6 +683,200 @@ describe('Topic\'s', function () { }); }); + describe('unread', function () { + var socketTopics = require('../src/socket.io/topics'); + var tid; + var mainPid; + var uid; + before(function (done) { + async.parallel({ + topic: function (next) { + topics.post({uid: topic.userId, title: 'unread topic', content: 'unread topic content', cid: topic.categoryId}, next); + }, + user: function (next) { + User.create({username: 'regularJoe'}, next); + } + }, function (err, results) { + assert.ifError(err); + tid = results.topic.topicData.tid; + mainPid = results.topic.postData.pid; + uid = results.user; + done(); + }); + }); + + it('should fail with invalid data', function (done) { + socketTopics.markUnread({uid: adminUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should fail if topic does not exist', function (done) { + socketTopics.markUnread({uid: adminUid}, 1231082, function (err) { + assert.equal(err.message, '[[error:no-topic]]'); + done(); + }); + }); + + it('should mark topic unread', function (done) { + socketTopics.markUnread({uid: adminUid}, tid, function (err) { + assert.ifError(err); + topics.hasReadTopic(tid, adminUid, function (err, hasRead) { + assert.ifError(err); + assert.equal(hasRead, false); + done(); + }); + }); + }); + + + it('should fail with invalid data', function (done) { + socketTopics.markAsRead({uid: 0}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + + it('should mark topic read', function (done) { + socketTopics.markAsRead({uid: adminUid}, [tid], function (err) { + assert.ifError(err); + topics.hasReadTopic(tid, adminUid, function (err, hasRead) { + assert.ifError(err); + assert(hasRead); + done(); + }); + }); + }); + + it('should fail with invalid data', function (done) { + socketTopics.markTopicNotificationsRead({uid: 0}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should mark topic notifications read', function (done) { + var socketPosts = require('../src/socket.io/posts'); + + async.waterfall([ + function (next) { + socketTopics.follow({uid: adminUid}, tid, next); + }, + function (next) { + socketPosts.reply({uid: uid}, {content: 'some content', tid: tid}, next); + }, + function (data, next) { + setTimeout(next, 2500); + }, + function (next) { + User.notifications.getUnreadCount(adminUid, next); + }, + function (count, next) { + assert.equal(count, 1); + socketTopics.markTopicNotificationsRead({uid: adminUid}, [tid], next); + }, + function (next) { + User.notifications.getUnreadCount(adminUid, next); + }, + function (count, next) { + assert.equal(count, 0); + next(); + } + ], function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should fail with invalid data', function (done) { + socketTopics.markAllRead({uid: 0}, null, function (err) { + assert.equal(err.message, '[[error:invalid-uid]]'); + done(); + }); + }); + + it('should mark all read', function (done) { + socketTopics.markUnread({uid: adminUid}, tid, function (err) { + assert.ifError(err); + socketTopics.markAllRead({uid: adminUid}, {}, function (err) { + assert.ifError(err); + topics.hasReadTopic(tid, adminUid, function (err, hasRead) { + assert.ifError(err); + assert(hasRead); + done(); + }); + }); + }); + }); + + it('should mark all read', function (done) { + socketTopics.markUnread({uid: adminUid}, tid, function (err) { + assert.ifError(err); + socketTopics.markCategoryTopicsRead({uid: adminUid}, topic.categoryId, function (err) { + assert.ifError(err); + topics.hasReadTopic(tid, adminUid, function (err, hasRead) { + assert.ifError(err); + assert(hasRead); + done(); + }); + }); + }); + }); + + + it('should fail with invalid data', function (done) { + socketTopics.markAsUnreadForAll({uid: adminUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-tid]]'); + done(); + }); + }); + + it('should fail with invalid data', function (done) { + socketTopics.markAsUnreadForAll({uid: 0}, [tid], function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + + it('should fail if user is not admin', function (done) { + socketTopics.markAsUnreadForAll({uid: uid}, [tid], function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + + it('should fail if topic does not exist', function (done) { + socketTopics.markAsUnreadForAll({uid: uid}, [12312313], function (err) { + assert.equal(err.message, '[[error:no-topic]]'); + done(); + }); + }); + + it('should mark topic unread for everyone', function (done) { + socketTopics.markAsUnreadForAll({uid: adminUid}, [tid], function (err) { + assert.ifError(err); + async.parallel({ + adminRead: function (next) { + topics.hasReadTopic(tid, adminUid, next); + }, + regularRead: function (next) { + topics.hasReadTopic(tid, uid, next); + } + }, function (err, results) { + assert.ifError(err); + assert.equal(results.adminRead, false); + assert.equal(results.regularRead, false); + done(); + }); + }); + }); + + + + + }); after(function (done) {