From 3509e12748d690da196d14ac369f312282a8708b Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Fri, 24 Feb 2017 09:22:21 +0000 Subject: [PATCH 1/6] Latest translations and fallbacks --- public/language/ja/topic.json | 6 +++--- public/language/ja/user.json | 6 +++--- public/language/tr/admin/settings/general.json | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) 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 From b7d5bc7dc8b84e4f17973fa1eec74d06a1a4a9e1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 24 Feb 2017 12:54:16 +0300 Subject: [PATCH 2/6] more group tests --- src/socket.io/groups.js | 4 +- test/groups.js | 86 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 5bff7ba944..ba274f32fb 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); diff --git a/test/groups.js b/test/groups.js index ff9ad3b013..a6dda5b887 100644 --- a/test/groups.js +++ b/test/groups.js @@ -536,17 +536,93 @@ 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 grant ownership to user', function (done) { socketGroups.grant({uid: adminUid}, {groupName: 'PrivateCanJoin', toUid: testUid}, function (err) { assert.ifError(err); @@ -580,6 +656,8 @@ describe('Groups', function () { }); }); + + }); describe('admin socket methods', function () { From 3a87f2566ae7b94890e7971e22d367dc6f467e1f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 24 Feb 2017 15:05:00 +0300 Subject: [PATCH 3/6] more group tests --- src/controllers/groups.js | 21 ++-- src/groups/membership.js | 4 +- src/socket.io/groups.js | 77 ++++++------- test/groups.js | 230 +++++++++++++++++++++++++++++++++++++- 4 files changed, 277 insertions(+), 55 deletions(-) diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 2cbbcc7c76..25a6d928db 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); + 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(null, { - groups: groups, - allowGroupCreation: parseInt(meta.config.allowGroupCreation, 10) === 1, - nextStart: stop + 1 - }); - }); + ], callback); }; groupsController.details = function (req, res, callback) { diff --git a/src/groups/membership.js b/src/groups/membership.js index e57847a61c..68a9724c9c 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'); @@ -138,7 +138,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), @@ -147,7 +146,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/socket.io/groups.js b/src/socket.io/groups.js index ba274f32fb..47453b6e0a 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -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,13 +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); + async.waterfall([ + function (next) { + groups.ownership.isOwner(data.uid, data.groupName, next); + }, + function (isOwner, next) { + groups.kick(data.uid, data.groupName, isOwner, next); } - groups.kick(data.uid, data.groupName, isOwner, callback); - }); - + ], callback); }); SocketGroups.create = function (socket, data, callback) { @@ -199,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 || {}; @@ -242,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; @@ -261,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); + 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(null, {users: users, nextStart: data.after + 10}); - }); + ], callback); }; SocketGroups.cover = {}; diff --git a/test/groups.js b/test/groups.js index a6dda5b887..6e8bf6e0b7 100644 --- a/test/groups.js +++ b/test/groups.js @@ -433,7 +433,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]]'); @@ -623,6 +622,99 @@ describe('Groups', function () { }); }); + 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); @@ -645,6 +737,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); @@ -656,8 +755,130 @@ 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 () { @@ -895,6 +1116,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]]'); From ca1fe557c7937f08164b1376bab0bcdf426b2e44 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 24 Feb 2017 16:26:19 +0300 Subject: [PATCH 4/6] topic controller tests --- src/controllers/topics.js | 2 +- src/privileges/topics.js | 13 +-- test/topics.js | 182 ++++++++++++++++++++++++++++++++++---- 3 files changed, 175 insertions(+), 22 deletions(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 1224de5bcd..0677c0a8c3 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -299,7 +299,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/privileges/topics.js b/src/privileges/topics.js index 39ad054462..2350299e72 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -63,13 +63,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/test/topics.js b/test/topics.js index 9f430c995a..cfb77ed581 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); } @@ -182,7 +183,7 @@ describe('Topic\'s', function () { }); }); - + it('should not receive errors', function (done) { topics.getTopicData(newTopic.tid, done); }); @@ -202,7 +203,7 @@ describe('Topic\'s', function () { done(); }); }); - + describe('.getTopicWithPosts', function () { it('should get a topic with posts and other data', function (done) { topics.getTopicData(newTopic.tid, function (err, topicData) { @@ -672,26 +673,177 @@ 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; From 04b07e292ced9dab1520fc0929763265c47dd643 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 24 Feb 2017 17:34:34 +0300 Subject: [PATCH 5/6] thumb tests --- src/controllers/uploads.js | 74 +++++++++++++++++++------------------- test/uploads.js | 45 +++++++++++++++++++++++ 2 files changed, 82 insertions(+), 37 deletions(-) diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index 923e3cf7d8..22e533a33d 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -6,7 +6,6 @@ var async = require('async'); var nconf = require('nconf'); var validator = require('validator'); var winston = require('winston'); -var mime = require('mime'); var meta = require('../meta'); var file = require('../file'); @@ -31,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); @@ -138,26 +137,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, @@ -166,8 +163,8 @@ uploadsController.uploadThumb = function (req, res, next) { } uploadFile(req.uid, uploadedFile, next); - }); - }); + } + ], next); }, next); }; @@ -186,12 +183,14 @@ uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) { }, callback); } - file.isFileTypeAllowed(uploadedFile.path, function (err) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + file.isFileTypeAllowed(uploadedFile.path, next); + }, + function (next) { + saveFileToLocal(uploadedFile, next); } - saveFileToLocal(uploadedFile, callback); - }); + ], callback); }; function uploadFile(uid, uploadedFile, callback) { @@ -230,17 +229,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); + 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(null, { - url: nconf.get('relative_path') + upload.url, - path: upload.path, - name: uploadedFile.name - }); - }); + ], callback); } function deleteTempFiles(files) { diff --git a/test/uploads.js b/test/uploads.js index f8a3738c8f..7ac1b595a2 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -88,6 +88,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; @@ -101,6 +115,37 @@ describe('Upload Controllers', function () { }); }); + 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(); + }); + }); + }); From 048eb8a320a217c5e692c598c86a7661bd5eb2c5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 24 Feb 2017 18:06:23 +0300 Subject: [PATCH 6/6] some helper tests --- public/src/app.js | 1 - public/src/modules/helpers.js | 2 +- test/template-helpers.js | 55 +++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 test/template-helpers.js diff --git a/public/src/app.js b/public/src/app.js index 0006db25d5..85bb5065aa 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 3035d29fc4..b1ac2e183e 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -7,7 +7,7 @@ exports = module.exports/* = SemVer*/; } - var helpers = {}; + var helpers = exports; helpers.displayMenuItem = function (data, index) { var item = data.navigation[index]; 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(); + }); + +});