diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json index 3a70be2b0f..0719e1d021 100644 --- a/public/language/ja/topic.json +++ b/public/language/ja/topic.json @@ -39,8 +39,8 @@ "markAsUnreadForAll.success": "すべてのスレッドを未読にしました。", "mark_unread": "未読としてマーク", "mark_unread.success": "スレッドは未読にマークされました。", - "watch": "ウオッチ", - "unwatch": "ウオッチ解除", + "watch": "ウォッチ", + "unwatch": "ウォッチ解除", "watch.title": "新しい投稿の通知を受ける", "unwatch.title": "このスレッドの通知を停止します", "share_this_post": "投稿を共有", @@ -107,7 +107,7 @@ "more_guests": "ゲストさんが%1人", "users_and_others": "%1と他は%2", "sort_by": "並び替え", - "oldest_to_newest": "古い\bものから新しい順", + "oldest_to_newest": "古いものから新しい順", "newest_to_oldest": "新しいものから古い順", "most_votes": "最も投票された順", "most_posts": "最も投稿された順", diff --git a/public/language/ja/user.json b/public/language/ja/user.json index 5ddd0fd082..dad3e17d58 100644 --- a/public/language/ja/user.json +++ b/public/language/ja/user.json @@ -51,7 +51,7 @@ "change_password": "パスワードを変更", "change_password_error": "無効のパスワード!", "change_password_error_wrong_current": "現在のパスワードは正しくありません!", - "change_password_error_length": "パスワードは短い過ぎです!", + "change_password_error_length": "パスワードが短過ぎです!", "change_password_error_match": "パスワードは一致しません!", "change_password_error_privileges": "パスワードを更新する権限はありません。", "change_password_success": "パスワードを更新しました!", @@ -79,7 +79,7 @@ "digest_monthly": "マンスリー", "send_chat_notifications": "オンラインではない時に新しいチャットメッセージを受信した場合、通知メールを送信する。", "send_post_notifications": "購読中のスレッドに返信があった場合、メールで通知する。", - "settings-require-reload": "変化がありましてブラウザを更新する必要があります。ここを押して、ページ更新します。", + "settings-require-reload": "設定を変更するにはページを更新する必要があります。ここを押して、ページを更新します。", "has_no_follower": "フォロワーはまだいません :(", "follows_no_one": "フォロー中のユーザーはまだいません :(", "has_no_posts": "このユーザーはまだ一つも投稿していません", @@ -106,7 +106,7 @@ "delay_image_loading": "画像読み込みを遅延させる", "image_load_delay_help": "有効の場合、スレッド内の画像はスクロールされるまで読み込みません", "scroll_to_my_post": "返信を投稿した後、新しい投稿を表示する", - "follow_topics_you_reply_to": "あなたが返信するスレッドをウォッチ", + "follow_topics_you_reply_to": "あなたが返信したスレッドをウォッチする", "follow_topics_you_create": "あなたが作成したスレッドをウォッチする", "grouptitle": "グループ題名", "no-group-title": "グループ名がありません", diff --git a/public/language/tr/admin/settings/general.json b/public/language/tr/admin/settings/general.json index e612ea5d63..2c3ef42d5c 100644 --- a/public/language/tr/admin/settings/general.json +++ b/public/language/tr/admin/settings/general.json @@ -3,29 +3,29 @@ "title": "Site Başlığı", "title.name": "Topluluk İsmi", "title.show-in-header": "Show Site Title in Header", - "browser-title": "Browser Title", + "browser-title": "Tarayıcı Başlığı", "browser-title-help": "If no browser title is specified, the site title will be used", "title-layout": "Title Layout", "title-layout-help": "Define how the browser title will be structured ie. {pageTitle} | {browserTitle}", "description.placeholder": "A short description about your community", "description": "Site Açıklaması", - "keywords": "Site Keywords", + "keywords": "Site Anahtar Kelimeler", "keywords-placeholder": "Keywords describing your community, comma-separated", "logo": "Site Logo", - "logo.image": "Image", + "logo.image": "Görsel", "logo.image-placeholder": "Path to a logo to display on forum header", "logo.upload": "Yükle", "logo.url": "URL", "logo.url-placeholder": "The URL of the site logo", "logo.url-help": "When the logo is clicked, send users to this address. If left blank, user will be sent to the forum index.", - "logo.alt-text": "Alt Text", + "logo.alt-text": "Alt Yazı", "log.alt-text-placeholder": "Alternative text for accessibility", "favicon": "Favicon", "favicon.upload": "Yükle", "touch-icon": "Homescreen/Touch Icon", "touch-icon.upload": "Yükle", "touch-icon.help": "Recommended size and format: 192x192, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.", - "outgoing-links": "Outgoing Links", + "outgoing-links": "Harici Bağlantılar", "outgoing-links.warning-page": "Use Outgoing Links Warning Page", - "search-default-sort-by": "Search default sort by" + "search-default-sort-by": "Aramada varsayılan sıralama" } \ No newline at end of file diff --git a/public/src/app.js b/public/src/app.js index 7112d281da..b478bc459c 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -77,7 +77,6 @@ app.cacheBuster = null; require(['taskbar', 'helpers', 'forum/pagination'], function (taskbar, helpers, pagination) { taskbar.init(); - // templates.js helpers helpers.register(); pagination.init(); diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index ea8c29d339..b7cdfaa950 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -1,7 +1,14 @@ -'use strict'; - (function (exports) { - var helpers = {}; + 'use strict'; + + /* globals define, utils, config */ + + // export the class if we are in a Node-like system. + if (typeof module === 'object' && module.exports === exports) { + exports = module.exports/* = SemVer*/; + } + + var helpers = exports; helpers.displayMenuItem = function (data, index) { var item = data.navigation[index]; diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 5fdc140826..fb780c1b97 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -32,17 +32,18 @@ groupsController.getGroupsFromSet = function (uid, sort, start, stop, callback) set = 'groups:visible:createtime'; } - groups.getGroupsFromSet(set, uid, start, stop, function (err, groups) { - if (err) { - return callback(err); - } - - callback(null, { - groups: groups, - allowGroupCreation: parseInt(meta.config.allowGroupCreation, 10) === 1, - nextStart: stop + 1, - }); - }); + async.waterfall([ + function (next) { + groups.getGroupsFromSet(set, uid, start, stop, next); + }, + function (groupsData, next) { + next(null, { + groups: groupsData, + allowGroupCreation: parseInt(meta.config.allowGroupCreation, 10) === 1, + nextStart: stop + 1, + }); + }, + ], callback); }; groupsController.details = function (req, res, callback) { diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 2d8d9bff1e..1d813868ae 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -298,7 +298,7 @@ topicsController.teaser = function (req, res, next) { var tid = req.params.topic_id; if (!utils.isNumber(tid)) { - return next(new Error('[[error:invalid-tid]]')); + return next(); } async.waterfall([ diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index 03538faee7..25375e91a9 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -30,7 +30,7 @@ uploadsController.upload = function (req, res, filesIterator) { deleteTempFiles(files); if (err) { - return res.status(500).send(err.message); + return res.status(500).json({ path: req.path, error: err.message }); } res.status(200).send(images); @@ -136,26 +136,24 @@ uploadsController.uploadThumb = function (req, res, next) { } uploadsController.upload(req, res, function (uploadedFile, next) { - file.isFileTypeAllowed(uploadedFile.path, function (err) { - if (err) { - return next(err); - } - - if (!uploadedFile.type.match(/image./)) { - return next(new Error('[[error:invalid-file]]')); - } - - var size = parseInt(meta.config.topicThumbSize, 10) || 120; - image.resizeImage({ - path: uploadedFile.path, - extension: path.extname(uploadedFile.name), - width: size, - height: size, - }, function (err) { - if (err) { - return next(err); + async.waterfall([ + function (next) { + if (!uploadedFile.type.match(/image./)) { + return next(new Error('[[error:invalid-file]]')); } + file.isFileTypeAllowed(uploadedFile.path, next); + }, + function (next) { + var size = parseInt(meta.config.topicThumbSize, 10) || 120; + image.resizeImage({ + path: uploadedFile.path, + extension: path.extname(uploadedFile.name), + width: size, + height: size, + }, next); + }, + function (next) { if (plugins.hasListeners('filter:uploadImage')) { return plugins.fireHook('filter:uploadImage', { image: uploadedFile, @@ -164,8 +162,8 @@ uploadsController.uploadThumb = function (req, res, next) { } uploadFile(req.uid, uploadedFile, next); - }); - }); + }, + ], next); }, next); }; @@ -184,12 +182,14 @@ uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) { }, callback); } - file.isFileTypeAllowed(uploadedFile.path, function (err) { - if (err) { - return callback(err); - } - saveFileToLocal(uploadedFile, callback); - }); + async.waterfall([ + function (next) { + file.isFileTypeAllowed(uploadedFile.path, next); + }, + function (next) { + saveFileToLocal(uploadedFile, next); + }, + ], callback); }; function uploadFile(uid, uploadedFile, callback) { @@ -228,17 +228,18 @@ function saveFileToLocal(uploadedFile, callback) { filename = Date.now() + '-' + validator.escape(filename.replace(path.extname(uploadedFile.name) || '', '')).substr(0, 255) + extension; - file.saveFileToLocal(filename, 'files', uploadedFile.path, function (err, upload) { - if (err) { - return callback(err); - } - - callback(null, { - url: nconf.get('relative_path') + upload.url, - path: upload.path, - name: uploadedFile.name, - }); - }); + async.waterfall([ + function (next) { + file.saveFileToLocal(filename, 'files', uploadedFile.path, next); + }, + function (upload, next) { + next(null, { + url: nconf.get('relative_path') + upload.url, + path: upload.path, + name: uploadedFile.name, + }); + }, + ], callback); } function deleteTempFiles(files) { diff --git a/src/groups/membership.js b/src/groups/membership.js index 567c5b7dcc..31a32da17c 100644 --- a/src/groups/membership.js +++ b/src/groups/membership.js @@ -1,6 +1,6 @@ 'use strict'; -var async = require('async'); +var async = require('async'); var winston = require('winston'); var _ = require('underscore'); @@ -137,7 +137,6 @@ module.exports = function (Groups) { }; Groups.acceptMembership = function (groupName, uid, callback) { - // Note: For simplicity, this method intentially doesn't check the caller uid for ownership! async.waterfall([ async.apply(db.setRemove, 'group:' + groupName + ':pending', uid), async.apply(db.setRemove, 'group:' + groupName + ':invited', uid), @@ -146,7 +145,6 @@ module.exports = function (Groups) { }; Groups.rejectMembership = function (groupName, uid, callback) { - // Note: For simplicity, this method intentially doesn't check the caller uid for ownership! async.parallel([ async.apply(db.setRemove, 'group:' + groupName + ':pending', uid), async.apply(db.setRemove, 'group:' + groupName + ':invited', uid), diff --git a/src/privileges/topics.js b/src/privileges/topics.js index 16c652ceb3..49920b4bb8 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -62,13 +62,14 @@ module.exports = function (privileges) { }; privileges.topics.can = function (privilege, tid, uid, 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) { + privileges.categories.can(privilege, cid, uid, next); } - - privileges.categories.can(privilege, cid, uid, callback); - }); + ], callback); }; privileges.topics.filterTids = function (privilege, tids, uid, callback) { diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 16240a506b..ed6cb00180 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -1,6 +1,6 @@ 'use strict'; -var async = require('async'); +var async = require('async'); var groups = require('../groups'); var meta = require('../meta'); @@ -77,7 +77,7 @@ function isOwner(next) { isAdmin: async.apply(user.isAdministrator, socket.uid), isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName), }, function (err, results) { - if (err || (!isOwner && !results.isAdmin)) { + if (err || (!results.isOwner && !results.isAdmin)) { return callback(err || new Error('[[error:no-privileges]]')); } next(socket, data, callback); @@ -141,22 +141,25 @@ SocketGroups.issueMassInvite = isOwner(function (socket, data, callback) { if (!data || !data.usernames || !data.groupName) { return callback(new Error('[[error:invalid-data]]')); } - var usernames = data.usernames.split(','); + var usernames = String(data.usernames).split(','); usernames = usernames.map(function (username) { return username && username.trim(); }); - user.getUidsByUsernames(usernames, function (err, uids) { - if (err) { - return callback(err); - } - uids = uids.filter(function (uid) { - return !!uid && parseInt(uid, 10); - }); - async.eachSeries(uids, function (uid, next) { - groups.invite(data.groupName, uid, next); - }, callback); - }); + async.waterfall([ + function (next) { + user.getUidsByUsernames(usernames, next); + }, + function (uids, next) { + uids = uids.filter(function (uid) { + return !!uid && parseInt(uid, 10); + }); + + async.eachSeries(uids, function (uid, next) { + groups.invite(data.groupName, uid, next); + }, next); + }, + ], callback); }); SocketGroups.rescindInvite = isOwner(function (socket, data, callback) { @@ -181,12 +184,14 @@ SocketGroups.kick = isOwner(function (socket, data, callback) { return callback(new Error('[[error:cant-kick-self]]')); } - groups.ownership.isOwner(data.uid, data.groupName, function (err, isOwner) { - if (err) { - return callback(err); - } - groups.kick(data.uid, data.groupName, isOwner, callback); - }); + async.waterfall([ + function (next) { + groups.ownership.isOwner(data.uid, data.groupName, next); + }, + function (isOwner, next) { + groups.kick(data.uid, data.groupName, isOwner, next); + }, + ], callback); }); SocketGroups.create = function (socket, data, callback) { @@ -198,32 +203,19 @@ SocketGroups.create = function (socket, data, callback) { return callback(new Error('[[error:invalid-group-name]]')); } - data.ownerUid = socket.uid; groups.create(data, callback); }; -SocketGroups.delete = function (socket, data, callback) { +SocketGroups.delete = isOwner(function (socket, data, callback) { if (data.groupName === 'administrators' || data.groupName === 'registered-users' || data.groupName === 'Global Moderators') { return callback(new Error('[[error:not-allowed]]')); } - async.parallel({ - isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName), - isAdmin: async.apply(user.isAdministrator, socket.uid), - }, function (err, checks) { - if (err) { - return callback(err); - } - if (!checks.isOwner && !checks.isAdmin) { - return callback(new Error('[[error:no-privileges]]')); - } - - groups.destroy(data.groupName, callback); - }); -}; + groups.destroy(data.groupName, callback); +}); SocketGroups.search = function (socket, data, callback) { data.options = data.options || {}; @@ -241,7 +233,7 @@ SocketGroups.search = function (socket, data, callback) { SocketGroups.loadMore = function (socket, data, callback) { if (!data.sort || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { - return callback(); + return callback(new Error('[[error:invalid-data]]')); } var groupsPerPage = 9; @@ -260,13 +252,17 @@ SocketGroups.loadMoreMembers = function (socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } data.after = parseInt(data.after, 10); - user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, function (err, users) { - if (err) { - return callback(err); - } - - callback(null, { users: users, nextStart: data.after + 10 }); - }); + async.waterfall([ + function (next) { + user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, next); + }, + function (users, next) { + next(null, { + users: users, + nextStart: data.after + 10, + }); + }, + ], callback); }; SocketGroups.cover = {}; diff --git a/test/groups.js b/test/groups.js index 71a7bbca5a..4d587b3aad 100644 --- a/test/groups.js +++ b/test/groups.js @@ -432,7 +432,6 @@ describe('Groups', function () { var socketGroups = require('../src/socket.io/groups'); var meta = require('../src/meta'); - it('should error if data is null', function (done) { socketGroups.before({ uid: 0 }, 'groups.join', null, function (err) { assert.equal(err.message, '[[error:invalid-data]]'); @@ -535,17 +534,186 @@ describe('Groups', function () { }); }); - it('should accept membership of user', function (done) { - socketGroups.accept({ uid: adminUid }, { groupName: 'PrivateCanJoin', toUid: testUid }, function (err) { + it('should reject membership of user', function (done) { + socketGroups.reject({ uid: adminUid }, { groupName: 'PrivateCanJoin', toUid: testUid }, function (err) { assert.ifError(err); - Groups.isMember(testUid, 'PrivateCanJoin', function (err, isMember) { + Groups.isInvited(testUid, 'PrivateCanJoin', function (err, invited) { assert.ifError(err); - assert(isMember); + assert.equal(invited, false); done(); }); }); }); + it('should error if not owner or admin', function (done) { + socketGroups.accept({ uid: 0 }, { groupName: 'PrivateCanJoin', toUid: testUid }, function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + + it('should accept membership of user', function (done) { + socketGroups.join({ uid: testUid }, { groupName: 'PrivateCanJoin' }, function (err) { + assert.ifError(err); + socketGroups.accept({ uid: adminUid }, { groupName: 'PrivateCanJoin', toUid: testUid }, function (err) { + assert.ifError(err); + Groups.isMember(testUid, 'PrivateCanJoin', function (err, isMember) { + assert.ifError(err); + assert(isMember); + done(); + }); + }); + }); + }); + + it('should reject/accept all memberships requests', function (done) { + function requestMembership(uids, callback) { + async.series([ + function (next) { + socketGroups.join({ uid: uids.uid1 }, { groupName: 'PrivateCanJoin' }, next); + }, + function (next) { + socketGroups.join({ uid: uids.uid2 }, { groupName: 'PrivateCanJoin' }, next); + }, + ], function (err) { + callback(err); + }); + } + var uids; + async.waterfall([ + function (next) { + async.parallel({ + uid1: function (next) { + User.create({ username: 'groupuser1' }, next); + }, + uid2: function (next) { + User.create({ username: 'groupuser2' }, next); + }, + }, next); + }, + function (results, next) { + uids = results; + requestMembership(results, next); + }, + function (next) { + socketGroups.rejectAll({ uid: adminUid }, { groupName: 'PrivateCanJoin' }, next); + }, + function (next) { + Groups.getPending('PrivateCanJoin', next); + }, + function (pending, next) { + assert.equal(pending.length, 0); + requestMembership(uids, next); + }, + function (next) { + socketGroups.acceptAll({ uid: adminUid }, { groupName: 'PrivateCanJoin' }, next); + }, + function (next) { + Groups.isMembers([uids.uid1, uids.uid2], 'PrivateCanJoin', next); + }, + function (isMembers, next) { + assert(isMembers[0]); + assert(isMembers[1]); + next(); + }, + ], function (err) { + done(err); + }); + }); + + it('should issue invite to user', function (done) { + User.create({ username: 'invite1' }, function (err, uid) { + assert.ifError(err); + socketGroups.issueInvite({ uid: adminUid }, { groupName: 'PrivateCanJoin', toUid: uid }, function (err) { + assert.ifError(err); + Groups.isInvited(uid, 'PrivateCanJoin', function (err, isInvited) { + assert.ifError(err); + assert(isInvited); + done(); + }); + }); + }); + }); + + it('should fail with invalid data', function (done) { + socketGroups.issueMassInvite({ uid: adminUid }, { groupName: 'PrivateCanJoin', usernames: null }, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should issue mass invite to users', function (done) { + User.create({ username: 'invite2' }, function (err, uid) { + assert.ifError(err); + socketGroups.issueMassInvite({ uid: adminUid }, { groupName: 'PrivateCanJoin', usernames: 'invite1, invite2' }, function (err) { + assert.ifError(err); + Groups.isInvited(uid, 'PrivateCanJoin', function (err, isInvited) { + assert.ifError(err); + assert(isInvited); + done(); + }); + }); + }); + }); + + it('should rescind invite', function (done) { + User.create({ username: 'invite3' }, function (err, uid) { + assert.ifError(err); + socketGroups.issueInvite({ uid: adminUid }, { groupName: 'PrivateCanJoin', toUid: uid }, function (err) { + assert.ifError(err); + socketGroups.rescindInvite({ uid: adminUid }, { groupName: 'PrivateCanJoin', toUid: uid }, function (err) { + assert.ifError(err); + Groups.isInvited(uid, 'PrivateCanJoin', function (err, isInvited) { + assert.ifError(err); + assert(!isInvited); + done(); + }); + }); + }); + }); + }); + + it('should error if user is not invited', function (done) { + socketGroups.acceptInvite({ uid: adminUid }, { groupName: 'PrivateCanJoin' }, function (err) { + assert.equal(err.message, '[[error:not-invited]]'); + done(); + }); + }); + + it('should accept invite', function (done) { + User.create({ username: 'invite4' }, function (err, uid) { + assert.ifError(err); + socketGroups.issueInvite({ uid: adminUid }, { groupName: 'PrivateCanJoin', toUid: uid }, function (err) { + assert.ifError(err); + socketGroups.acceptInvite({ uid: uid }, { groupName: 'PrivateCanJoin' }, function (err) { + assert.ifError(err); + Groups.isMember(uid, 'PrivateCanJoin', function (err, isMember) { + assert.ifError(err); + assert(isMember); + done(); + }); + }); + }); + }); + }); + + it('should reject invite', function (done) { + User.create({ username: 'invite5' }, function (err, uid) { + assert.ifError(err); + socketGroups.issueInvite({ uid: adminUid }, { groupName: 'PrivateCanJoin', toUid: uid }, function (err) { + assert.ifError(err); + socketGroups.rejectInvite({ uid: uid }, { groupName: 'PrivateCanJoin' }, function (err) { + assert.ifError(err); + Groups.isInvited(uid, 'PrivateCanJoin', function (err, isInvited) { + assert.ifError(err); + assert(!isInvited); + done(); + }); + }); + }); + }); + }); + it('should grant ownership to user', function (done) { socketGroups.grant({ uid: adminUid }, { groupName: 'PrivateCanJoin', toUid: testUid }, function (err) { assert.ifError(err); @@ -568,6 +736,13 @@ describe('Groups', function () { }); }); + it('should fail to kick user with invalid data', function (done) { + socketGroups.kick({ uid: adminUid }, { groupName: 'PrivateCanJoin', uid: adminUid }, function (err) { + assert.equal(err.message, '[[error:cant-kick-self]]'); + done(); + }); + }); + it('should kick user from group', function (done) { socketGroups.kick({ uid: adminUid }, { groupName: 'PrivateCanJoin', uid: testUid }, function (err) { assert.ifError(err); @@ -578,6 +753,131 @@ describe('Groups', function () { }); }); }); + + it('should fail to create group with invalid data', function (done) { + socketGroups.create({ uid: 0 }, {}, function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + + it('should fail to create group if group creation is disabled', function (done) { + var oldValue = meta.config.allowGroupCreation; + meta.config.allowGroupCreation = 0; + socketGroups.create({ uid: 1 }, {}, function (err) { + assert.equal(err.message, '[[error:group-creation-disabled]]'); + meta.config.allowGroupCreation = oldValue; + done(); + }); + }); + + it('should fail to create group if name is privilege group', function (done) { + var oldValue = meta.config.allowGroupCreation; + meta.config.allowGroupCreation = 1; + socketGroups.create({ uid: 1 }, { name: 'cid:1:privileges:groups:find' }, function (err) { + assert.equal(err.message, '[[error:invalid-group-name]]'); + meta.config.allowGroupCreation = oldValue; + done(); + }); + }); + + + it('should create/update group', function (done) { + var oldValue = meta.config.allowGroupCreation; + meta.config.allowGroupCreation = 1; + socketGroups.create({ uid: adminUid }, { name: 'createupdategroup' }, function (err, groupData) { + meta.config.allowGroupCreation = oldValue; + assert.ifError(err); + assert(groupData); + var data = { + groupName: 'createupdategroup', + values: { + name: 'renamedupdategroup', + 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('renamedupdategroup', {}, function (err, groupData) { + assert.ifError(err); + assert.equal(groupData.name, 'renamedupdategroup'); + 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(); + }); + }); + }); + }); + + it('should delete group', function (done) { + socketGroups.delete({ uid: adminUid }, { groupName: 'renamedupdategroup' }, function (err) { + assert.ifError(err); + Groups.exists('renamedupdategroup', function (err, exists) { + assert.ifError(err); + assert(!exists); + done(); + }); + }); + }); + + it('should fail to delete group if name is special', function (done) { + socketGroups.delete({ uid: adminUid }, { groupName: 'administrators' }, function (err) { + assert.equal(err.message, '[[error:not-allowed]]'); + done(); + }); + }); + + it('should fail to delete group if name is special', function (done) { + socketGroups.delete({ uid: adminUid }, { groupName: 'registered-users' }, function (err) { + assert.equal(err.message, '[[error:not-allowed]]'); + done(); + }); + }); + + it('should fail to delete group if name is special', function (done) { + socketGroups.delete({ uid: adminUid }, { groupName: 'Global Moderators' }, function (err) { + assert.equal(err.message, '[[error:not-allowed]]'); + done(); + }); + }); + + it('should fail to load more groups with invalid data', function (done) { + socketGroups.loadMore({ uid: adminUid }, {}, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should load more groups', function (done) { + socketGroups.loadMore({ uid: adminUid }, { after: 0, sort: 'count' }, function (err, data) { + assert.ifError(err); + assert(Array.isArray(data.groups)); + done(); + }); + }); + + it('should fail to load more members with invalid data', function (done) { + socketGroups.loadMoreMembers({ uid: adminUid }, {}, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should load more members', function (done) { + socketGroups.loadMoreMembers({ uid: adminUid }, { after: 0, groupName: 'PrivateCanJoin' }, function (err, data) { + assert.ifError(err); + assert(Array.isArray(data.users)); + done(); + }); + }); }); describe('admin socket methods', function () { @@ -815,6 +1115,13 @@ describe('Groups', function () { }); }); + it('should fail to remove cover if not logged in', function (done) { + socketGroups.cover.remove({ uid: 0 }, { groupName: 'Test' }, function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + it('should fail to remove cover if not owner', function (done) { socketGroups.cover.remove({ uid: regularUid }, { groupName: 'Test' }, function (err) { assert.equal(err.message, '[[error:no-privileges]]'); diff --git a/test/template-helpers.js b/test/template-helpers.js new file mode 100644 index 0000000000..87502f9e40 --- /dev/null +++ b/test/template-helpers.js @@ -0,0 +1,55 @@ +'use strict'; + +var async = require('async'); +var assert = require('assert'); + +var db = require('./mocks/databasemock'); +var helpers = require('../public/src/modules/helpers'); + +describe('helpers', function () { + + + it('should return false if item doesn\'t exist', function (done) { + var flag = helpers.displayMenuItem({navigation: []}, 0); + assert(!flag); + done(); + }); + + + it('should return false if route is /users and privateUserInfo is on and user is not logged in', function (done) { + var flag = helpers.displayMenuItem({ + navigation: [{route: '/users'}], + privateUserInfo: true, + config: { + loggedIn: false + } + }, 0); + assert(!flag); + done(); + }); + + it('should return false if route is /tags and privateTagListing is on and user is not logged in', function (done) { + var flag = helpers.displayMenuItem({ + navigation: [{route: '/tags'}], + privateTagListing: true, + config: { + loggedIn: false + } + }, 0); + assert(!flag); + done(); + }); + + it('should stringify object', function (done) { + var str = helpers.stringify({a: 'herp < derp > and & quote "'}); + assert.equal(str, '{"a":"herp < derp > and & quote \\""}'); + done(); + }); + + it('should escape html', function (done) { + var str = helpers.escape('gdkfhgk < some > and &'); + assert.equal(str, 'gdkfhgk < some > and &'); + done(); + }); + +}); diff --git a/test/topics.js b/test/topics.js index 911d8bb216..2573225511 100644 --- a/test/topics.js +++ b/test/topics.js @@ -10,6 +10,7 @@ var topics = require('../src/topics'); var categories = require('../src/categories'); var User = require('../src/user'); var groups = require('../src/groups'); +var helpers = require('./helpers'); var socketPosts = require('../src/socket.io/posts'); describe('Topic\'s', function () { @@ -19,7 +20,7 @@ describe('Topic\'s', function () { before(function (done) { groups.resetCache(); - User.create({ username: 'admin' }, function (err, uid) { + User.create({ username: 'admin', password: '123456' }, function (err, uid) { if (err) { return done(err); } @@ -669,26 +670,176 @@ describe('Topic\'s', function () { }); }); - it('should load topic', function (done) { - topics.post({ - uid: topic.userId, - title: 'topic for controller test', - content: 'topic content', - cid: topic.categoryId, - thumb: 'http://i.imgur.com/64iBdBD.jpg', - }, function (err, result) { - assert.ifError(err); - assert.ok(result); - var request = require('request'); - request(nconf.get('url') + '/topic/' + result.topicData.slug, function (err, response, body) { + describe('controller', function () { + var request = require('request'); + var topicData; + + before(function (done) { + topics.post({ + uid: topic.userId, + title: 'topic for controller test', + content: 'topic content', + cid: topic.categoryId, + thumb: 'http://i.imgur.com/64iBdBD.jpg', + }, function (err, result) { + assert.ifError(err); + assert.ok(result); + topicData = result.topicData; + done(); + }); + }); + + it('should load topic', function (done) { + request(nconf.get('url') + '/topic/' + topicData.slug, function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 200); + assert(body); + done(); + }); + }); + + it('should 404 if post index is invalid', function (done) { + request(nconf.get('url') + '/topic/' + topicData.slug + '/derp', function (err, response) { + assert.ifError(err); + assert.equal(response.statusCode, 404); + done(); + }); + }); + + it('should 404 if topic does not exist', function (done) { + request(nconf.get('url') + '/topic/123123/does-not-exist', function (err, response) { + assert.ifError(err); + assert.equal(response.statusCode, 404); + done(); + }); + }); + + it('should 401 if not allowed to read as guest', function (done) { + var privileges = require('../src/privileges'); + privileges.categories.rescind(['read'], topicData.cid, 'guests', function (err) { + assert.ifError(err); + request(nconf.get('url') + '/api/topic/' + topicData.slug, function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 401); + assert(body); + privileges.categories.give(['read'], topicData.cid, 'guests', done); + }); + }); + }); + + it('should redirect to correct topic if slug is missing', function (done) { + request(nconf.get('url') + '/topic/' + topicData.tid + '/herpderp/1?page=2', function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 200); + assert(body); + done(); + }); + }); + + it('should redirect if post index is out of range', function (done) { + request(nconf.get('url') + '/api/topic/' + topicData.slug + '/-1', function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 308); + assert.equal(body, '"/topic/13/topic-for-controller-test"'); + done(); + }); + }); + + it('should 404 if page is out of bounds', function (done) { + var meta = require('../src/meta'); + meta.config.usePagination = 1; + request(nconf.get('url') + '/topic/' + topicData.slug + '?page=100', function (err, response) { + assert.ifError(err); + assert.equal(response.statusCode, 404); + done(); + }); + }); + + it('should mark topic read', function (done) { + helpers.loginUser('admin', '123456', function (err, jar) { + assert.ifError(err); + request(nconf.get('url') + '/topic/' + topicData.slug, { + jar: jar, + }, function (err, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + topics.hasReadTopics([topicData.tid], adminUid, function (err, hasRead) { + assert.ifError(err); + assert.equal(hasRead[0], true); + done(); + }); + }); + }); + }); + + it('should 404 if tid is not a number', function (done) { + request(nconf.get('url') + '/api/topic/teaser/nan', { json: true }, function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 404); + done(); + }); + }); + + it('should 403 if cant read', function (done) { + request(nconf.get('url') + '/api/topic/teaser/' + 123123, { json: true }, function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 403); + assert.equal(body, '[[error:no-privileges]]'); + + done(); + }); + }); + + it('should load topic teaser', function (done) { + request(nconf.get('url') + '/api/topic/teaser/' + topicData.tid, { json: true }, function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 200); + assert(body); + assert.equal(body.tid, topicData.tid); + assert.equal(body.content, 'topic content'); + assert(body.user); + assert(body.topic); + assert(body.category); + done(); + }); + }); + + + it('should 404 if tid is not a number', function (done) { + request(nconf.get('url') + '/api/topic/pagination/nan', { json: true }, function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 404); + done(); + }); + }); + + it('should 404 if tid does not exist', function (done) { + request(nconf.get('url') + '/api/topic/pagination/1231231', { json: true }, function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 404); + done(); + }); + }); + + it('should load pagination', function (done) { + request(nconf.get('url') + '/api/topic/pagination/' + topicData.tid, { json: true }, function (err, response, body) { assert.ifError(err); assert.equal(response.statusCode, 200); assert(body); + assert.deepEqual(body, { + prev: { page: 1, active: false }, + next: { page: 1, active: false }, + rel: [], + pages: [], + currentPage: 1, + pageCount: 1, + }); done(); }); }); }); + describe('infinitescroll', function () { var socketTopics = require('../src/socket.io/topics'); var tid; diff --git a/test/uploads.js b/test/uploads.js index 513f333884..0c4ca8d1d6 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -87,6 +87,20 @@ describe('Upload Controllers', function () { }); }); + it('should resize and upload an image to a post', function (done) { + var oldValue = meta.config.maximumImageWidth; + meta.config.maximumImageWidth = 10; + helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), { cid: cid }, jar, csrf_token, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(Array.isArray(body)); + assert(body[0].path); + assert(body[0].url); + meta.config.maximumImageWidth = oldValue; + done(); + }); + }); + it('should upload a file to a post', function (done) { meta.config.allowFileUploads = 1; @@ -99,6 +113,37 @@ describe('Upload Controllers', function () { done(); }); }); + + it('should fail if topic thumbs are disabled', function (done) { + helpers.uploadFile(nconf.get('url') + '/api/topic/thumb/upload', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 500); + assert.equal(body.error, '[[error:topic-thumbnails-are-disabled]]'); + done(); + }); + }); + + it('should fail if file is not image', function (done) { + meta.config.allowTopicsThumbnail = 1; + helpers.uploadFile(nconf.get('url') + '/api/topic/thumb/upload', path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 500); + assert.equal(body.error, '[[error:invalid-file]]'); + done(); + }); + }); + + it('should upload topic thumb', function (done) { + meta.config.allowTopicsThumbnail = 1; + helpers.uploadFile(nconf.get('url') + '/api/topic/thumb/upload', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(Array.isArray(body)); + assert(body[0].path); + assert(body[0].url); + done(); + }); + }); });