'use strict' ;
const assert = require ( 'assert' ) ;
const async = require ( 'async' ) ;
const request = require ( 'request-promise-native' ) ;
const nconf = require ( 'nconf' ) ;
const path = require ( 'path' ) ;
const util = require ( 'util' ) ;
const sleep = util . promisify ( setTimeout ) ;
const db = require ( './mocks/databasemock' ) ;
const topics = require ( '../src/topics' ) ;
const posts = require ( '../src/posts' ) ;
const categories = require ( '../src/categories' ) ;
const privileges = require ( '../src/privileges' ) ;
const user = require ( '../src/user' ) ;
const groups = require ( '../src/groups' ) ;
const socketPosts = require ( '../src/socket.io/posts' ) ;
const apiPosts = require ( '../src/api/posts' ) ;
const apiTopics = require ( '../src/api/topics' ) ;
const meta = require ( '../src/meta' ) ;
const file = require ( '../src/file' ) ;
const helpers = require ( './helpers' ) ;
const utils = require ( '../src/utils' ) ;
describe ( 'Post\'s' , ( ) => {
let voterUid ;
let voteeUid ;
let globalModUid ;
let postData ;
let topicData ;
let cid ;
before ( ( done ) => {
async . series ( {
voterUid : function ( next ) {
user . create ( { username : 'upvoter' } , next ) ;
} ,
voteeUid : function ( next ) {
user . create ( { username : 'upvotee' } , next ) ;
} ,
globalModUid : function ( next ) {
user . create ( { username : 'globalmod' , password : 'globalmodpwd' } , next ) ;
} ,
category : function ( next ) {
categories . create ( {
name : 'Test Category' ,
description : 'Test category created by testing script' ,
} , next ) ;
} ,
} , ( err , results ) => {
if ( err ) {
return done ( err ) ;
}
voterUid = results . voterUid ;
voteeUid = results . voteeUid ;
globalModUid = results . globalModUid ;
cid = results . category . cid ;
topics . post ( {
uid : results . voteeUid ,
cid : results . category . cid ,
title : 'Test Topic Title' ,
content : 'The content of test topic' ,
} , ( err , data ) => {
if ( err ) {
return done ( err ) ;
}
postData = data . postData ;
topicData = data . topicData ;
groups . join ( 'Global Moderators' , globalModUid , done ) ;
} ) ;
} ) ;
} ) ;
it ( 'should update category teaser properly' , async ( ) => {
const getCategoriesAsync = async ( ) => await request ( ` ${ nconf . get ( 'url' ) } /api/categories ` , { json : true } ) ;
const postResult = await topics . post ( { uid : globalModUid , cid : cid , title : 'topic title' , content : '123456789' } ) ;
let data = await getCategoriesAsync ( ) ;
assert . equal ( data . categories [ 0 ] . teaser . pid , postResult . postData . pid ) ;
assert . equal ( data . categories [ 0 ] . posts [ 0 ] . content , '123456789' ) ;
assert . equal ( data . categories [ 0 ] . posts [ 0 ] . pid , postResult . postData . pid ) ;
const newUid = await user . create ( { username : 'teaserdelete' } ) ;
const newPostResult = await topics . post ( { uid : newUid , cid : cid , title : 'topic title' , content : 'xxxxxxxx' } ) ;
data = await getCategoriesAsync ( ) ;
assert . equal ( data . categories [ 0 ] . teaser . pid , newPostResult . postData . pid ) ;
assert . equal ( data . categories [ 0 ] . posts [ 0 ] . content , 'xxxxxxxx' ) ;
assert . equal ( data . categories [ 0 ] . posts [ 0 ] . pid , newPostResult . postData . pid ) ;
await user . delete ( 1 , newUid ) ;
data = await getCategoriesAsync ( ) ;
assert . equal ( data . categories [ 0 ] . teaser . pid , postResult . postData . pid ) ;
assert . equal ( data . categories [ 0 ] . posts [ 0 ] . content , '123456789' ) ;
assert . equal ( data . categories [ 0 ] . posts [ 0 ] . pid , postResult . postData . pid ) ;
} ) ;
it ( 'should change owner of post and topic properly' , async ( ) => {
const oldUid = await user . create ( { username : 'olduser' } ) ;
const newUid = await user . create ( { username : 'newuser' } ) ;
const postResult = await topics . post ( { uid : oldUid , cid : cid , title : 'change owner' , content : 'original post' } ) ;
const postData = await topics . reply ( { uid : oldUid , tid : postResult . topicData . tid , content : 'firstReply' } ) ;
const pid1 = postResult . postData . pid ;
const pid2 = postData . pid ;
assert . deepStrictEqual ( await db . sortedSetScores ( ` tid: ${ postResult . topicData . tid } :posters ` , [ oldUid , newUid ] ) , [ 2 , null ] ) ;
await posts . changeOwner ( [ pid1 , pid2 ] , newUid ) ;
assert . deepStrictEqual ( await db . sortedSetScores ( ` tid: ${ postResult . topicData . tid } :posters ` , [ oldUid , newUid ] ) , [ null , 2 ] ) ;
assert . deepStrictEqual ( await posts . isOwner ( [ pid1 , pid2 ] , oldUid ) , [ false , false ] ) ;
assert . deepStrictEqual ( await posts . isOwner ( [ pid1 , pid2 ] , newUid ) , [ true , true ] ) ;
assert . strictEqual ( await user . getUserField ( oldUid , 'postcount' ) , 0 ) ;
assert . strictEqual ( await user . getUserField ( newUid , 'postcount' ) , 2 ) ;
assert . strictEqual ( await user . getUserField ( oldUid , 'topiccount' ) , 0 ) ;
assert . strictEqual ( await user . getUserField ( newUid , 'topiccount' ) , 1 ) ;
assert . strictEqual ( await db . sortedSetScore ( 'users:postcount' , oldUid ) , 0 ) ;
assert . strictEqual ( await db . sortedSetScore ( 'users:postcount' , newUid ) , 2 ) ;
assert . strictEqual ( await topics . isOwner ( postResult . topicData . tid , oldUid ) , false ) ;
assert . strictEqual ( await topics . isOwner ( postResult . topicData . tid , newUid ) , true ) ;
assert . strictEqual ( await topics . getTopicField ( postResult . topicData . tid , 'postercount' ) , 1 ) ;
} ) ;
it ( 'should fail to change owner if new owner does not exist' , async ( ) => {
try {
await posts . changeOwner ( [ 1 ] , '9999999' ) ;
} catch ( err ) {
assert . strictEqual ( err . message , '[[error:no-user]]' ) ;
}
} ) ;
it ( 'should fail to change owner if user is not authorized' , async ( ) => {
try {
await socketPosts . changeOwner ( { uid : voterUid } , { pids : [ 1 , 2 ] , toUid : voterUid } ) ;
} catch ( err ) {
assert . strictEqual ( err . message , '[[error:no-privileges]]' ) ;
}
} ) ;
it ( 'should return falsy if post does not exist' , ( done ) => {
posts . getPostData ( 9999 , ( err , postData ) => {
assert . ifError ( err ) ;
assert . equal ( postData , null ) ;
done ( ) ;
} ) ;
} ) ;
describe ( 'voting' , ( ) => {
it ( 'should fail to upvote post if group does not have upvote permission' , async ( ) => {
await privileges . categories . rescind ( [ 'groups:posts:upvote' , 'groups:posts:downvote' ] , cid , 'registered-users' ) ;
let err ;
try {
await apiPosts . upvote ( { uid : voterUid } , { pid : postData . pid , room _id : 'topic_1' } ) ;
} catch ( _err ) {
err = _err ;
}
assert . equal ( err . message , '[[error:no-privileges]]' ) ;
try {
await apiPosts . downvote ( { uid : voterUid } , { pid : postData . pid , room _id : 'topic_1' } ) ;
} catch ( _err ) {
err = _err ;
}
assert . equal ( err . message , '[[error:no-privileges]]' ) ;
await privileges . categories . give ( [ 'groups:posts:upvote' , 'groups:posts:downvote' ] , cid , 'registered-users' ) ;
} ) ;
it ( 'should upvote a post' , async ( ) => {
const result = await apiPosts . upvote ( { uid : voterUid } , { pid : postData . pid , room _id : 'topic_1' } ) ;
assert . equal ( result . post . upvotes , 1 ) ;
assert . equal ( result . post . downvotes , 0 ) ;
assert . equal ( result . post . votes , 1 ) ;
assert . equal ( result . user . reputation , 1 ) ;
const data = await posts . hasVoted ( postData . pid , voterUid ) ;
assert . equal ( data . upvoted , true ) ;
assert . equal ( data . downvoted , false ) ;
} ) ;
it ( 'should add the pid to the :votes sorted set for that user' , async ( ) => {
const cid = await posts . getCidByPid ( postData . pid ) ;
const { uid , pid } = postData ;
const score = await db . sortedSetScore ( ` cid: ${ cid } :uid: ${ uid } :pids:votes ` , pid ) ;
assert . strictEqual ( score , 1 ) ;
} ) ;
it ( 'should get voters' , ( done ) => {
socketPosts . getVoters ( { uid : globalModUid } , { pid : postData . pid , cid : cid } , ( err , data ) => {
assert . ifError ( err ) ;
assert . equal ( data . upvoteCount , 1 ) ;
assert . equal ( data . downvoteCount , 0 ) ;
assert ( Array . isArray ( data . upvoters ) ) ;
assert . equal ( data . upvoters [ 0 ] . username , 'upvoter' ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should get upvoters' , ( done ) => {
socketPosts . getUpvoters ( { uid : globalModUid } , [ postData . pid ] , ( err , data ) => {
assert . ifError ( err ) ;
assert . equal ( data [ 0 ] . otherCount , 0 ) ;
assert . equal ( data [ 0 ] . usernames , 'upvoter' ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should fail to get upvoters if user does not have read privilege' , async ( ) => {
await privileges . categories . rescind ( [ 'groups:topics:read' ] , cid , 'guests' ) ;
await assert . rejects ( socketPosts . getUpvoters ( { uid : 0 } , [ postData . pid ] ) , {
message : '[[error:no-privileges]]' ,
} ) ;
await privileges . categories . give ( [ 'groups:topics:read' ] , cid , 'guests' ) ;
} ) ;
it ( 'should unvote a post' , async ( ) => {
const result = await apiPosts . unvote ( { uid : voterUid } , { pid : postData . pid , room _id : 'topic_1' } ) ;
assert . equal ( result . post . upvotes , 0 ) ;
assert . equal ( result . post . downvotes , 0 ) ;
assert . equal ( result . post . votes , 0 ) ;
assert . equal ( result . user . reputation , 0 ) ;
const data = await posts . hasVoted ( postData . pid , voterUid ) ;
assert . equal ( data . upvoted , false ) ;
assert . equal ( data . downvoted , false ) ;
} ) ;
it ( 'should downvote a post' , async ( ) => {
const result = await apiPosts . downvote ( { uid : voterUid } , { pid : postData . pid , room _id : 'topic_1' } ) ;
assert . equal ( result . post . upvotes , 0 ) ;
assert . equal ( result . post . downvotes , 1 ) ;
assert . equal ( result . post . votes , - 1 ) ;
assert . equal ( result . user . reputation , - 1 ) ;
const data = await posts . hasVoted ( postData . pid , voterUid ) ;
assert . equal ( data . upvoted , false ) ;
assert . equal ( data . downvoted , true ) ;
} ) ;
it ( 'should add the pid to the :votes sorted set for that user' , async ( ) => {
const cid = await posts . getCidByPid ( postData . pid ) ;
const { uid , pid } = postData ;
const score = await db . sortedSetScore ( ` cid: ${ cid } :uid: ${ uid } :pids:votes ` , pid ) ;
assert . strictEqual ( score , - 1 ) ;
} ) ;
it ( 'should prevent downvoting more than total daily limit' , async ( ) => {
const oldValue = meta . config . downvotesPerDay ;
meta . config . downvotesPerDay = 1 ;
let err ;
const p1 = await topics . reply ( {
uid : voteeUid ,
tid : topicData . tid ,
content : 'raw content' ,
} ) ;
try {
await apiPosts . downvote ( { uid : voterUid } , { pid : p1 . pid , room _id : 'topic_1' } ) ;
} catch ( _err ) {
err = _err ;
}
assert . equal ( err . message , '[[error:too-many-downvotes-today, 1]]' ) ;
meta . config . downvotesPerDay = oldValue ;
} ) ;
it ( 'should prevent downvoting target user more than total daily limit' , async ( ) => {
const oldValue = meta . config . downvotesPerUserPerDay ;
meta . config . downvotesPerUserPerDay = 1 ;
let err ;
const p1 = await topics . reply ( {
uid : voteeUid ,
tid : topicData . tid ,
content : 'raw content' ,
} ) ;
try {
await apiPosts . downvote ( { uid : voterUid } , { pid : p1 . pid , room _id : 'topic_1' } ) ;
} catch ( _err ) {
err = _err ;
}
assert . equal ( err . message , '[[error:too-many-downvotes-today-user, 1]]' ) ;
meta . config . downvotesPerUserPerDay = oldValue ;
} ) ;
} ) ;
describe ( 'bookmarking' , ( ) => {
it ( 'should bookmark a post' , async ( ) => {
const data = await apiPosts . bookmark ( { uid : voterUid } , { pid : postData . pid , room _id : ` topic_ ${ postData . tid } ` } ) ;
assert . equal ( data . isBookmarked , true ) ;
const hasBookmarked = await posts . hasBookmarked ( postData . pid , voterUid ) ;
assert . equal ( hasBookmarked , true ) ;
} ) ;
it ( 'should unbookmark a post' , async ( ) => {
const data = await apiPosts . unbookmark ( { uid : voterUid } , { pid : postData . pid , room _id : ` topic_ ${ postData . tid } ` } ) ;
assert . equal ( data . isBookmarked , false ) ;
const hasBookmarked = await posts . hasBookmarked ( [ postData . pid ] , voterUid ) ;
assert . equal ( hasBookmarked [ 0 ] , false ) ;
} ) ;
} ) ;
describe ( 'post tools' , ( ) => {
it ( 'should error if data is invalid' , ( done ) => {
socketPosts . loadPostTools ( { uid : globalModUid } , null , ( err ) => {
assert . equal ( err . message , '[[error:invalid-data]]' ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should load post tools' , ( done ) => {
socketPosts . loadPostTools ( { uid : globalModUid } , { pid : postData . pid , cid : cid } , ( err , data ) => {
assert . ifError ( err ) ;
assert ( data . posts . display _edit _tools ) ;
assert ( data . posts . display _delete _tools ) ;
assert ( data . posts . display _moderator _tools ) ;
assert ( data . posts . display _move _tools ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
describe ( 'delete/restore/purge' , ( ) => {
async function createTopicWithReply ( ) {
const topicPostData = await topics . post ( {
uid : voterUid ,
cid : cid ,
title : 'topic to delete/restore/purge' ,
content : 'A post to delete/restore/purge' ,
} ) ;
const replyData = await topics . reply ( {
uid : voterUid ,
tid : topicPostData . topicData . tid ,
timestamp : Date . now ( ) ,
content : 'A post to delete/restore and purge' ,
} ) ;
return [ topicPostData , replyData ] ;
}
let tid ;
let mainPid ;
let replyPid ;
before ( async ( ) => {
const [ topicPostData , replyData ] = await createTopicWithReply ( ) ;
tid = topicPostData . topicData . tid ;
mainPid = topicPostData . postData . pid ;
replyPid = replyData . pid ;
await privileges . categories . give ( [ 'groups:purge' ] , cid , 'registered-users' ) ;
} ) ;
it ( 'should error with invalid data' , async ( ) => {
try {
await apiPosts . delete ( { uid : voterUid } , null ) ;
} catch ( err ) {
return assert . equal ( err . message , '[[error:invalid-data]]' ) ;
}
assert ( false ) ;
} ) ;
it ( 'should delete a post' , async ( ) => {
await apiPosts . delete ( { uid : voterUid } , { pid : replyPid , tid : tid } ) ;
const isDeleted = await posts . getPostField ( replyPid , 'deleted' ) ;
assert . strictEqual ( isDeleted , 1 ) ;
} ) ;
it ( 'should not see post content if global mod does not have posts:view_deleted privilege' , ( done ) => {
async . waterfall ( [
function ( next ) {
user . create ( { username : 'global mod' , password : '123456' } , next ) ;
} ,
function ( uid , next ) {
groups . join ( 'Global Moderators' , uid , next ) ;
} ,
function ( next ) {
privileges . categories . rescind ( [ 'groups:posts:view_deleted' ] , cid , 'Global Moderators' , next ) ;
} ,
async ( ) => {
const { jar } = await helpers . loginUser ( 'global mod' , '123456' ) ;
const { posts } = await request ( ` ${ nconf . get ( 'url' ) } /api/topic/ ${ tid } ` , { jar , json : true } ) ;
assert . equal ( posts [ 1 ] . content , '[[topic:post_is_deleted]]' ) ;
await privileges . categories . give ( [ 'groups:posts:view_deleted' ] , cid , 'Global Moderators' ) ;
} ,
] , done ) ;
} ) ;
it ( 'should restore a post' , async ( ) => {
await apiPosts . restore ( { uid : voterUid } , { pid : replyPid , tid : tid } ) ;
const isDeleted = await posts . getPostField ( replyPid , 'deleted' ) ;
assert . strictEqual ( isDeleted , 0 ) ;
} ) ;
it ( 'should delete topic if last main post is deleted' , async ( ) => {
const data = await topics . post ( { uid : voterUid , cid : cid , title : 'test topic' , content : 'test topic' } ) ;
await apiPosts . delete ( { uid : globalModUid } , { pid : data . postData . pid } ) ;
const deleted = await topics . getTopicField ( data . topicData . tid , 'deleted' ) ;
assert . strictEqual ( deleted , 1 ) ;
} ) ;
it ( 'should purge posts and purge topic' , async ( ) => {
const [ topicPostData , replyData ] = await createTopicWithReply ( ) ;
await apiPosts . purge ( { uid : voterUid } , { pid : replyData . pid } ) ;
await apiPosts . purge ( { uid : voterUid } , { pid : topicPostData . postData . pid } ) ;
const pidExists = await posts . exists ( replyData . pid ) ;
assert . strictEqual ( pidExists , false ) ;
const tidExists = await topics . exists ( topicPostData . topicData . tid ) ;
assert . strictEqual ( tidExists , false ) ;
} ) ;
} ) ;
describe ( 'edit' , ( ) => {
let pid ;
let replyPid ;
let tid ;
before ( ( done ) => {
topics . post ( {
uid : voterUid ,
cid : cid ,
title : 'topic to edit' ,
content : 'A post to edit' ,
tags : [ 'nodebb' ] ,
} , ( err , data ) => {
assert . ifError ( err ) ;
pid = data . postData . pid ;
tid = data . topicData . tid ;
topics . reply ( {
uid : voterUid ,
tid : tid ,
timestamp : Date . now ( ) ,
content : 'A reply to edit' ,
} , ( err , data ) => {
assert . ifError ( err ) ;
replyPid = data . pid ;
privileges . categories . give ( [ 'groups:posts:edit' ] , cid , 'registered-users' , done ) ;
} ) ;
} ) ;
} ) ;
it ( 'should error if user is not logged in' , async ( ) => {
try {
await apiPosts . edit ( { uid : 0 } , { pid : pid , content : 'gg' } ) ;
} catch ( err ) {
return assert . equal ( err . message , '[[error:not-logged-in]]' ) ;
}
assert ( false ) ;
} ) ;
it ( 'should error if data is invalid or missing' , async ( ) => {
try {
await apiPosts . edit ( { uid : voterUid } , { } ) ;
} catch ( err ) {
return assert . equal ( err . message , '[[error:invalid-data]]' ) ;
}
assert ( false ) ;
} ) ;
it ( 'should error if title is too short' , async ( ) => {
try {
await apiPosts . edit ( { uid : voterUid } , { pid : pid , content : 'edited post content' , title : 'a' } ) ;
} catch ( err ) {
return assert . equal ( err . message , ` [[error:title-too-short, ${ meta . config . minimumTitleLength } ]] ` ) ;
}
assert ( false ) ;
} ) ;
it ( 'should error if title is too long' , async ( ) => {
const longTitle = new Array ( meta . config . maximumTitleLength + 2 ) . join ( 'a' ) ;
try {
await apiPosts . edit ( { uid : voterUid } , { pid : pid , content : 'edited post content' , title : longTitle } ) ;
} catch ( err ) {
return assert . equal ( err . message , ` [[error:title-too-long, ${ meta . config . maximumTitleLength } ]] ` ) ;
}
assert ( false ) ;
} ) ;
it ( 'should error with too few tags' , async ( ) => {
const oldValue = meta . config . minimumTagsPerTopic ;
meta . config . minimumTagsPerTopic = 1 ;
try {
await apiPosts . edit ( { uid : voterUid } , { pid : pid , content : 'edited post content' , tags : [ ] } ) ;
} catch ( err ) {
assert . equal ( err . message , ` [[error:not-enough-tags, ${ meta . config . minimumTagsPerTopic } ]] ` ) ;
meta . config . minimumTagsPerTopic = oldValue ;
return ;
}
assert ( false ) ;
} ) ;
it ( 'should error with too many tags' , async ( ) => {
const tags = [ ] ;
for ( let i = 0 ; i < meta . config . maximumTagsPerTopic + 1 ; i += 1 ) {
tags . push ( ` tag ${ i } ` ) ;
}
try {
await apiPosts . edit ( { uid : voterUid } , { pid : pid , content : 'edited post content' , tags : tags } ) ;
} catch ( err ) {
return assert . equal ( err . message , ` [[error:too-many-tags, ${ meta . config . maximumTagsPerTopic } ]] ` ) ;
}
assert ( false ) ;
} ) ;
it ( 'should error if content is too short' , async ( ) => {
try {
await apiPosts . edit ( { uid : voterUid } , { pid : pid , content : 'e' } ) ;
} catch ( err ) {
return assert . equal ( err . message , ` [[error:content-too-short, ${ meta . config . minimumPostLength } ]] ` ) ;
}
assert ( false ) ;
} ) ;
it ( 'should error if content is too long' , async ( ) => {
const longContent = new Array ( meta . config . maximumPostLength + 2 ) . join ( 'a' ) ;
try {
await apiPosts . edit ( { uid : voterUid } , { pid : pid , content : longContent } ) ;
} catch ( err ) {
return assert . equal ( err . message , ` [[error:content-too-long, ${ meta . config . maximumPostLength } ]] ` ) ;
}
assert ( false ) ;
} ) ;
it ( 'should edit post' , async ( ) => {
const data = await apiPosts . edit ( { uid : voterUid } , {
pid : pid ,
content : 'edited post content' ,
title : 'edited title' ,
tags : [ 'edited' ] ,
} ) ;
assert . strictEqual ( data . content , 'edited post content' ) ;
assert . strictEqual ( data . editor , voterUid ) ;
assert . strictEqual ( data . topic . title , 'edited title' ) ;
assert . strictEqual ( data . topic . tags [ 0 ] . value , 'edited' ) ;
const res = await db . getObject ( ` post: ${ pid } ` ) ;
assert ( ! res . hasOwnProperty ( 'bookmarks' ) ) ;
} ) ;
it ( 'should disallow post editing for new users if post was made past the threshold for editing' , async ( ) => {
meta . config . newbiePostEditDuration = 1 ;
await sleep ( 1000 ) ;
try {
await apiPosts . edit ( { uid : voterUid } , { pid : pid , content : 'edited post content again' , title : 'edited title again' , tags : [ 'edited-twice' ] } ) ;
} catch ( err ) {
assert . equal ( err . message , '[[error:post-edit-duration-expired, 1]]' ) ;
meta . config . newbiePostEditDuration = 3600 ;
return ;
}
assert ( false ) ;
} ) ;
it ( 'should edit a deleted post' , async ( ) => {
await apiPosts . delete ( { uid : voterUid } , { pid : pid , tid : tid } ) ;
const data = await apiPosts . edit ( { uid : voterUid } , { pid : pid , content : 'edited deleted content' , title : 'edited deleted title' , tags : [ 'deleted' ] } ) ;
assert . equal ( data . content , 'edited deleted content' ) ;
assert . equal ( data . editor , voterUid ) ;
assert . equal ( data . topic . title , 'edited deleted title' ) ;
assert . equal ( data . topic . tags [ 0 ] . value , 'deleted' ) ;
} ) ;
it ( 'should edit a reply post' , async ( ) => {
const data = await apiPosts . edit ( { uid : voterUid } , { pid : replyPid , content : 'edited reply' } ) ;
assert . equal ( data . content , 'edited reply' ) ;
assert . equal ( data . editor , voterUid ) ;
assert . equal ( data . topic . isMainPost , false ) ;
assert . equal ( data . topic . renamed , false ) ;
} ) ;
it ( 'should return diffs' , ( done ) => {
posts . diffs . get ( replyPid , 0 , ( err , data ) => {
assert . ifError ( err ) ;
assert ( Array . isArray ( data ) ) ;
assert ( data [ 0 ] . pid , replyPid ) ;
assert ( data [ 0 ] . patch ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should load diffs and reconstruct post' , ( done ) => {
posts . diffs . load ( replyPid , 0 , voterUid , ( err , data ) => {
assert . ifError ( err ) ;
assert . equal ( data . content , 'A reply to edit' ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should not allow guests to view diffs' , async ( ) => {
let err = { } ;
try {
await apiPosts . getDiffs ( { uid : 0 } , { pid : 1 } ) ;
} catch ( _err ) {
err = _err ;
}
assert . strictEqual ( err . message , '[[error:no-privileges]]' ) ;
} ) ;
it ( 'should allow registered-users group to view diffs' , async ( ) => {
const data = await apiPosts . getDiffs ( { uid : 1 } , { pid : 1 } ) ;
assert . strictEqual ( 'boolean' , typeof data . editable ) ;
assert . strictEqual ( false , data . editable ) ;
assert . equal ( true , Array . isArray ( data . timestamps ) ) ;
assert . strictEqual ( 1 , data . timestamps . length ) ;
assert . equal ( true , Array . isArray ( data . revisions ) ) ;
assert . strictEqual ( data . timestamps . length , data . revisions . length ) ;
[ 'timestamp' , 'username' ] . every ( prop => Object . keys ( data . revisions [ 0 ] ) . includes ( prop ) ) ;
} ) ;
it ( 'should not delete first diff of a post' , async ( ) => {
const timestamps = await posts . diffs . list ( replyPid ) ;
await assert . rejects (
posts . diffs . delete ( replyPid , timestamps [ 0 ] , voterUid ) ,
{ message : '[[error:invalid-data]]' }
) ;
} ) ;
it ( 'should delete a post diff' , async ( ) => {
await apiPosts . edit ( { uid : voterUid } , { pid : replyPid , content : 'another edit has been made' } ) ;
await apiPosts . edit ( { uid : voterUid } , { pid : replyPid , content : 'most recent edit' } ) ;
const timestamp = ( await posts . diffs . list ( replyPid ) ) . pop ( ) ;
await posts . diffs . delete ( replyPid , timestamp , voterUid ) ;
const differentTimestamp = ( await posts . diffs . list ( replyPid ) ) . pop ( ) ;
assert . notStrictEqual ( timestamp , differentTimestamp ) ;
} ) ;
it ( 'should load (oldest) diff and reconstruct post correctly after a diff deletion' , async ( ) => {
const data = await posts . diffs . load ( replyPid , 0 , voterUid ) ;
assert . strictEqual ( data . content , 'A reply to edit' ) ;
} ) ;
} ) ;
describe ( 'move' , ( ) => {
let replyPid ;
let tid ;
let moveTid ;
before ( async ( ) => {
const topic1 = await topics . post ( {
uid : voterUid ,
cid : cid ,
title : 'topic 1' ,
content : 'some content' ,
} ) ;
tid = topic1 . topicData . tid ;
const topic2 = await topics . post ( {
uid : voterUid ,
cid : cid ,
title : 'topic 2' ,
content : 'some content' ,
} ) ;
moveTid = topic2 . topicData . tid ;
const reply = await topics . reply ( {
uid : voterUid ,
tid : tid ,
timestamp : Date . now ( ) ,
content : 'A reply to move' ,
} ) ;
replyPid = reply . pid ;
} ) ;
it ( 'should error if uid is not logged in' , async ( ) => {
try {
await apiPosts . move ( { uid : 0 } , { } ) ;
} catch ( err ) {
return assert . equal ( err . message , '[[error:not-logged-in]]' ) ;
}
assert ( false ) ;
} ) ;
it ( 'should error if data is invalid' , async ( ) => {
try {
await apiPosts . move ( { uid : globalModUid } , { } ) ;
} catch ( err ) {
return assert . equal ( err . message , '[[error:invalid-data]]' ) ;
}
assert ( false ) ;
} ) ;
it ( 'should error if user does not have move privilege' , async ( ) => {
try {
await apiPosts . move ( { uid : voterUid } , { pid : replyPid , tid : moveTid } ) ;
} catch ( err ) {
return assert . equal ( err . message , '[[error:no-privileges]]' ) ;
}
assert ( false ) ;
} ) ;
it ( 'should move a post' , async ( ) => {
await apiPosts . move ( { uid : globalModUid } , { pid : replyPid , tid : moveTid } ) ;
const tid = await posts . getPostField ( replyPid , 'tid' ) ;
assert ( tid , moveTid ) ;
} ) ;
it ( 'should fail to move post if not moderator of target category' , async ( ) => {
const cat1 = await categories . create ( { name : 'Test Category' , description : 'Test category created by testing script' } ) ;
const cat2 = await categories . create ( { name : 'Test Category' , description : 'Test category created by testing script' } ) ;
const result = await apiTopics . create ( { uid : globalModUid } , { title : 'target topic' , content : 'queued topic' , cid : cat2 . cid } ) ;
const modUid = await user . create ( { username : 'modofcat1' } ) ;
Webpack5 (#10311)
* feat: webpack 5 part 1
* fix: gruntfile fixes
* fix: fix taskbar warning
add app.importScript
copy public/src/modules to build folder
* refactor: remove commented old code
* feat: reenable admin
* fix: acp settings pages, fix sortable on manage categories
embedded require in html not allowed
* fix: bundle serialize/deserizeli so plugins dont break
* test: fixe util tests
* test: fix require path
* test: more test fixes
* test: require correct utils module
* test: require correct utils
* test: log stack
* test: fix db require blowing up tests
* test: move and disable bundle test
* refactor: add aliases
* test: disable testing route
* fix: move webpack modules necessary for build, into `dependencies`
* test: fix one more test
remove 500-embed.tpl
* fix: restore use of assets/nodebb.min.js, at least for now
* fix: remove unnecessary line break
* fix: point to proper ACP bundle
* test: maybe fix build test
* test: composer
* refactor: dont need dist
* refactor: more cleanup
use everything from build/public folder
* get rid of conditional import in app.js
* fix: ace
* refactor: cropper alias
* test: lint and test fixes
* lint: fix
* refactor: rename function to app.require
* refactor: go back to using app.require
* chore: use github branch
* chore: use webpack branch
* feat: webpack webinstaller
* feat: add chunkFile name with contenthash
* refactor: move hooks to top
* refactor: get rid of template500Function
* fix(deps): use webpack5 branch of 2factor plugin
* chore: tagging v2.0.0-beta.0 pre-release version :boom: :shipit: :tada: :rocket:
* refactor: disable cache on templates
loadTemplate is called once by benchpress and the result is cache internally
* refactor: add server side helpers.js
* feat: deprecate /plugins shorthand route, closes #10343
* refactor: use build/public for webpack
* test: fix filename
* fix: more specific selector
* lint: ignore
* refactor: fix comments
* test: add debug for random failing test
* refactor: cleanup
remove test page, remove dupe functions in utils.common
* lint: use relative path for now
* chore: bump prerelease version
* feat: add translateKeys
* fix: optional params
* fix: get rid of extra timeago files
* refactor: cleanup, require timeago locale earlier
remove translator.prepareDOM, it is in header.tpl html tag
* refactor: privileges system to use a Map in the backend instead of separate objects for keys and labels (#10378)
* refactor: privileges system to use a Map in the backend instead of separate objects for keys and labels
- Existing hooks are preserved (to be deprecated at a later date, possibly)
- New init hooks are called on NodeBB start, and provide a one-stop shop to add new privileges, instead of having to add to four different hooks
* docs: fix typo in comment
* test: spec changes
* refactor: privileges system to use a Map in the backend instead of separate objects for keys and labels (#10378)
* refactor: privileges system to use a Map in the backend instead of separate objects for keys and labels
- Existing hooks are preserved (to be deprecated at a later date, possibly)
- New init hooks are called on NodeBB start, and provide a one-stop shop to add new privileges, instead of having to add to four different hooks
* docs: fix typo in comment
* test: spec changes
* feat: allow app.require('bootbox'/'benchpressjs')
* refactor: require server side utils
* test: jquery ready
* change istaller to use build/public
* test: use document.addEventListener
* refactor: closes #10301
* refactor: generateTopicClass
* fix: column counts for other privileges
* fix: #10443, regression where sorted-list items did not render into the DOM in the predicted order [breaking]
* fix: typo in hook name
* refactor: introduce a generic autocomplete.init() method that can be called to add nodebb-style autocompletion but using different data sources (e.g. not user/groups/tags)
* fix: crash if `delay` not passed in (as it cannot be destructured)
* refactor: replace substr
* feat: set --panel-offset style in html element based on stored value in localStorage
* refactor: addDropupHandler() logic to be less naive
- Take into account height of the menu
- Don't apply dropUp logic if there's nothing in the dropdown
- Remove 'hidden' class (added by default in Persona for post tools) when menu items are added
closes #10423
* refactor: simplify utils.params [breaking]
Retrospective analysis of the usage of this method suggests that the options passed in are superfluous, and that only `url` is required. Using a browser built-in makes more sense to accomplish what this method sets out to do.
* feat: add support for returning full URLSearchParams for utils.params
* fix: utils.params() fallback handling
* fix: default empty obj for params()
* fix: remove \'loggedin\' and \'register\' qs parameters once they have been used, delay invocation of messages until ajaxify.end
* fix: utils.params() not allowing relative paths to be passed in
* refactor(DRY): new assertPasswordValidity utils method
* fix: incorrect error message returned on insufficient privilege on flag edit
* fix: read/update/delete access to flags API should be limited for moderators to only post flags in categories they moderate
- added failing tests and patched up middleware.assert.flags to fix
* refactor: flag api v3 tests to create new post and flags on every round
* fix: missing error:no-flag language key
* refactor: flags.canView to check flag existence, simplify middleware.assert.flag
* feat: flag deletion API endpoint, #10426
* feat: UI for flag deletion, closes #10426
* chore: update plugin versions
* chore: up emoji
* chore: update markdown
* chore: up emoji-android
* fix: regression caused by utils.params() refactor, supports arrays and pipes all values through utils.toType, adjusts tests to type check
Co-authored-by: Julian Lam <[email protected] >
3 years ago
const userPrivilegeList = await privileges . categories . getUserPrivilegeList ( ) ;
await privileges . categories . give ( userPrivilegeList , cat1 . cid , modUid ) ;
let err ;
try {
await apiPosts . move ( { uid : modUid } , { pid : replyPid , tid : result . tid } ) ;
} catch ( _err ) {
err = _err ;
}
assert . strictEqual ( err . message , '[[error:no-privileges]]' ) ;
} ) ;
} ) ;
describe ( 'getPostSummaryByPids' , ( ) => {
it ( 'should return empty array for empty pids' , ( done ) => {
posts . getPostSummaryByPids ( [ ] , 0 , { } , ( err , data ) => {
assert . ifError ( err ) ;
assert . equal ( data . length , 0 ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should get post summaries' , ( done ) => {
posts . getPostSummaryByPids ( [ postData . pid ] , 0 , { } , ( err , data ) => {
assert . ifError ( err ) ;
assert ( data [ 0 ] . user ) ;
assert ( data [ 0 ] . topic ) ;
assert ( data [ 0 ] . category ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should get recent poster uids' , ( done ) => {
topics . reply ( {
uid : voterUid ,
tid : topicData . tid ,
timestamp : Date . now ( ) ,
content : 'some content' ,
} , ( err ) => {
assert . ifError ( err ) ;
posts . getRecentPosterUids ( 0 , 1 , ( err , uids ) => {
assert . ifError ( err ) ;
assert ( Array . isArray ( uids ) ) ;
assert . equal ( uids . length , 2 ) ;
assert . equal ( uids [ 0 ] , voterUid ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
describe ( 'parse' , ( ) => {
it ( 'should not crash and return falsy if post data is falsy' , ( done ) => {
posts . parsePost ( null , ( err , postData ) => {
assert . ifError ( err ) ;
assert . strictEqual ( postData , null ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should store post content in cache' , ( done ) => {
const oldValue = global . env ;
global . env = 'production' ;
const postData = {
pid : 9999 ,
content : 'some post content' ,
} ;
posts . parsePost ( postData , ( err ) => {
assert . ifError ( err ) ;
posts . parsePost ( postData , ( err ) => {
assert . ifError ( err ) ;
global . env = oldValue ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should parse signature and remove links and images' , ( done ) => {
meta . config [ 'signatures:disableLinks' ] = 1 ;
meta . config [ 'signatures:disableImages' ] = 1 ;
const userData = {
signature : '<img src="boop"/><a href="link">test</a> derp' ,
} ;
posts . parseSignature ( userData , 1 , ( err , data ) => {
assert . ifError ( err ) ;
assert . equal ( data . userData . signature , 'test derp' ) ;
meta . config [ 'signatures:disableLinks' ] = 0 ;
meta . config [ 'signatures:disableImages' ] = 0 ;
done ( ) ;
} ) ;
} ) ;
it ( 'should turn relative links in post body to absolute urls' , ( done ) => {
const nconf = require ( 'nconf' ) ;
const content = '<a href="/users">test</a> <a href="youtube.com">youtube</a>' ;
const parsedContent = posts . relativeToAbsolute ( content , posts . urlRegex ) ;
assert . equal ( parsedContent , ` <a href=" ${ nconf . get ( 'base_url' ) } /users">test</a> <a href="//youtube.com">youtube</a> ` ) ;
done ( ) ;
} ) ;
it ( 'should turn relative links in post body to absolute urls' , ( done ) => {
const nconf = require ( 'nconf' ) ;
const content = '<a href="/users">test</a> <a href="youtube.com">youtube</a> some test <img src="/path/to/img"/>' ;
let parsedContent = posts . relativeToAbsolute ( content , posts . urlRegex ) ;
parsedContent = posts . relativeToAbsolute ( parsedContent , posts . imgRegex ) ;
assert . equal ( parsedContent , ` <a href=" ${ nconf . get ( 'base_url' ) } /users">test</a> <a href="//youtube.com">youtube</a> some test <img src=" ${ nconf . get ( 'base_url' ) } /path/to/img"/> ` ) ;
done ( ) ;
} ) ;
} ) ;
describe ( 'socket methods' , ( ) => {
let pid ;
before ( ( done ) => {
topics . reply ( {
uid : voterUid ,
tid : topicData . tid ,
timestamp : Date . now ( ) ,
content : 'raw content' ,
} , ( err , postData ) => {
assert . ifError ( err ) ;
pid = postData . pid ;
privileges . categories . rescind ( [ 'groups:topics:read' ] , cid , 'guests' , done ) ;
} ) ;
} ) ;
it ( 'should error with invalid data' , async ( ) => {
try {
await apiTopics . reply ( { uid : 0 } , null ) ;
assert ( false ) ;
} catch ( err ) {
assert . equal ( err . message , '[[error:invalid-data]]' ) ;
}
} ) ;
it ( 'should error with invalid tid' , async ( ) => {
try {
await apiTopics . reply ( { uid : 0 } , { tid : 0 , content : 'derp' } ) ;
assert ( false ) ;
} catch ( err ) {
assert . equal ( err . message , '[[error:invalid-data]]' ) ;
}
} ) ;
it ( 'should fail to get raw post because of privilege' , async ( ) => {
const content = await apiPosts . getRaw ( { uid : 0 } , { pid } ) ;
assert . strictEqual ( content , null ) ;
} ) ;
it ( 'should fail to get raw post because post is deleted' , async ( ) => {
await posts . setPostField ( pid , 'deleted' , 1 ) ;
const content = await apiPosts . getRaw ( { uid : voterUid } , { pid } ) ;
assert . strictEqual ( content , null ) ;
} ) ;
it ( 'should allow privileged users to view the deleted post\'s raw content' , async ( ) => {
await posts . setPostField ( pid , 'deleted' , 1 ) ;
const content = await apiPosts . getRaw ( { uid : globalModUid } , { pid } ) ;
assert . strictEqual ( content , 'raw content' ) ;
} ) ;
it ( 'should get raw post content' , async ( ) => {
await posts . setPostField ( pid , 'deleted' , 0 ) ;
const postContent = await apiPosts . getRaw ( { uid : voterUid } , { pid } ) ;
assert . equal ( postContent , 'raw content' ) ;
} ) ;
it ( 'should get post' , async ( ) => {
const postData = await apiPosts . get ( { uid : voterUid } , { pid } ) ;
assert ( postData ) ;
} ) ;
it ( 'should get post summary' , async ( ) => {
const summary = await apiPosts . getSummary ( { uid : voterUid } , { pid } ) ;
assert ( summary ) ;
} ) ;
it ( 'should get raw post content' , async ( ) => {
const postContent = await socketPosts . getRawPost ( { uid : voterUid } , pid ) ;
assert . equal ( postContent , 'raw content' ) ;
} ) ;
it ( 'should get post summary by index' , async ( ) => {
const summary = await socketPosts . getPostSummaryByIndex ( { uid : voterUid } , {
index : 1 ,
tid : topicData . tid ,
} ) ;
assert ( summary ) ;
} ) ;
it ( 'should get post timestamp by index' , async ( ) => {
const timestamp = await socketPosts . getPostTimestampByIndex ( { uid : voterUid } , {
index : 1 ,
tid : topicData . tid ,
} ) ;
assert ( utils . isNumber ( timestamp ) ) ;
} ) ;
it ( 'should get post timestamp by index' , async ( ) => {
const summary = await socketPosts . getPostSummaryByPid ( { uid : voterUid } , {
pid : pid ,
} ) ;
assert ( summary ) ;
} ) ;
it ( 'should get post category' , async ( ) => {
const postCid = await socketPosts . getCategory ( { uid : voterUid } , pid ) ;
assert . equal ( cid , postCid ) ;
} ) ;
it ( 'should get pid index' , async ( ) => {
const index = await socketPosts . getPidIndex ( { uid : voterUid } , { pid : pid , tid : topicData . tid , topicPostSort : 'oldest_to_newest' } ) ;
assert . equal ( index , 4 ) ;
} ) ;
it ( 'should get pid index' , async ( ) => {
const index = await apiPosts . getIndex ( { uid : voterUid } , { pid : pid , sort : 'oldest_to_newest' } ) ;
assert . strictEqual ( index , 4 ) ;
} ) ;
it ( 'should get pid index in reverse' , async ( ) => {
const postData = await topics . reply ( {
uid : voterUid ,
tid : topicData . tid ,
content : 'raw content' ,
} ) ;
const index = await apiPosts . getIndex ( { uid : voterUid } , { pid : postData . pid , sort : 'newest_to_oldest' } ) ;
assert . equal ( index , 1 ) ;
} ) ;
} ) ;
describe ( 'filterPidsByCid' , ( ) => {
it ( 'should return pids as is if cid is falsy' , ( done ) => {
posts . filterPidsByCid ( [ 1 , 2 , 3 ] , null , ( err , pids ) => {
assert . ifError ( err ) ;
assert . deepEqual ( [ 1 , 2 , 3 ] , pids ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should filter pids by single cid' , ( done ) => {
posts . filterPidsByCid ( [ postData . pid , 100 , 101 ] , cid , ( err , pids ) => {
assert . ifError ( err ) ;
assert . deepEqual ( [ postData . pid ] , pids ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should filter pids by multiple cids' , ( done ) => {
posts . filterPidsByCid ( [ postData . pid , 100 , 101 ] , [ cid , 2 , 3 ] , ( err , pids ) => {
assert . ifError ( err ) ;
assert . deepEqual ( [ postData . pid ] , pids ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should filter pids by multiple cids' , ( done ) => {
posts . filterPidsByCid ( [ postData . pid , 100 , 101 ] , [ cid ] , ( err , pids ) => {
assert . ifError ( err ) ;
assert . deepEqual ( [ postData . pid ] , pids ) ;
done ( ) ;
} ) ;
} ) ;
} ) ;
it ( 'should error if user does not exist' , ( done ) => {
user . isReadyToPost ( 21123123 , 1 , ( err ) => {
assert . equal ( err . message , '[[error:no-user]]' ) ;
done ( ) ;
} ) ;
} ) ;
describe ( 'post queue' , ( ) => {
let uid ;
let queueId ;
let topicQueueId ;
let jar ;
before ( ( done ) => {
meta . config . postQueue = 1 ;
user . create ( { username : 'newuser' } , ( err , _uid ) => {
assert . ifError ( err ) ;
uid = _uid ;
done ( ) ;
} ) ;
} ) ;
after ( ( done ) => {
meta . config . postQueue = 0 ;
meta . config . groupsExemptFromPostQueue = [ ] ;
done ( ) ;
} ) ;
it ( 'should add topic to post queue' , async ( ) => {
const result = await apiTopics . create ( { uid : uid } , { title : 'should be queued' , content : 'queued topic content' , cid : cid } ) ;
assert . strictEqual ( result . queued , true ) ;
assert . equal ( result . message , '[[success:post-queued]]' ) ;
topicQueueId = result . id ;
} ) ;
it ( 'should add reply to post queue' , async ( ) => {
const result = await apiTopics . reply ( { uid : uid } , { content : 'this is a queued reply' , tid : topicData . tid } ) ;
assert . strictEqual ( result . queued , true ) ;
assert . equal ( result . message , '[[success:post-queued]]' ) ;
queueId = result . id ;
} ) ;
it ( 'should load queued posts' , async ( ) => {
( { jar } = await helpers . loginUser ( 'globalmod' , 'globalmodpwd' ) ) ;
const { posts } = await request ( ` ${ nconf . get ( 'url' ) } /api/post-queue ` , { jar : jar , json : true } ) ;
assert . equal ( posts [ 0 ] . type , 'topic' ) ;
assert . equal ( posts [ 0 ] . data . content , 'queued topic content' ) ;
assert . equal ( posts [ 1 ] . type , 'reply' ) ;
assert . equal ( posts [ 1 ] . data . content , 'this is a queued reply' ) ;
} ) ;
it ( 'should error if data is invalid' , ( done ) => {
socketPosts . editQueuedContent ( { uid : globalModUid } , null , ( err ) => {
assert . equal ( err . message , '[[error:invalid-data]]' ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should edit post in queue' , async ( ) => {
await socketPosts . editQueuedContent ( { uid : globalModUid } , { id : queueId , content : 'newContent' } ) ;
const { posts } = await request ( ` ${ nconf . get ( 'url' ) } /api/post-queue ` , { jar : jar , json : true } ) ;
assert . equal ( posts [ 1 ] . type , 'reply' ) ;
assert . equal ( posts [ 1 ] . data . content , 'newContent' ) ;
} ) ;
it ( 'should edit topic title in queue' , async ( ) => {
await socketPosts . editQueuedContent ( { uid : globalModUid } , { id : topicQueueId , title : 'new topic title' } ) ;
const { posts } = await request ( ` ${ nconf . get ( 'url' ) } /api/post-queue ` , { jar : jar , json : true } ) ;
assert . equal ( posts [ 0 ] . type , 'topic' ) ;
assert . equal ( posts [ 0 ] . data . title , 'new topic title' ) ;
} ) ;
it ( 'should edit topic category in queue' , async ( ) => {
await socketPosts . editQueuedContent ( { uid : globalModUid } , { id : topicQueueId , cid : 2 } ) ;
const { posts } = await request ( ` ${ nconf . get ( 'url' ) } /api/post-queue ` , { jar : jar , json : true } ) ;
assert . equal ( posts [ 0 ] . type , 'topic' ) ;
assert . equal ( posts [ 0 ] . data . cid , 2 ) ;
await socketPosts . editQueuedContent ( { uid : globalModUid } , { id : topicQueueId , cid : cid } ) ;
} ) ;
it ( 'should prevent regular users from approving posts' , ( done ) => {
socketPosts . accept ( { uid : uid } , { id : queueId } , ( err ) => {
assert . equal ( err . message , '[[error:no-privileges]]' ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should prevent regular users from approving non existing posts' , ( done ) => {
socketPosts . accept ( { uid : uid } , { id : 123123 } , ( err ) => {
Bootstrap5 (#10894)
* chore: up deps
* chore: up composer
* fix(deps): bump 2factor to v7
* chore: up harmony
* chore: up harmony
* fix: missing await
* feat: allow middlewares to pass in template values via res.locals
* feat: buildAccountData middleware automatically added ot all account routes
* fix: properly allow values in res.locals.templateValues to be added to the template data
* refactor: user/blocks
* refactor(accounts): categories and consent
* feat: automatically 404 if exposeUid or exposeGroupName come up empty
* refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now
* fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization
* fix: move reputation removal check to accountHelpers method
* test: skip i18n tests if ref branch when present is not develop
* fix(deps): bump theme versions
* fix(deps): bump ntfy and 2factor
* chore: up harmony
* fix: add missing return
* fix: #11191, only focus on search input on md environments and up
* feat: allow file uploads on mobile chat
closes https://github.com/NodeBB/NodeBB/issues/11217
* chore: up themes
* chore: add lang string
* fix(deps): bump ntfy to 1.0.15
* refactor: use new if/each syntax
* chore: up composer
* fix: regression from user helper refactor
* chore: up harmony
* chore: up composer
* chore: up harmony
* chore: up harmony
* chore: up harmony
* chore: fix composer version
* feat: add increment helper
* chore: up harmony
* fix: #11228 no timestamps in future :hourglass:
* chore: up harmony
* check config.theme as well
fire action:posts.loaded after processing dom
* chore: up harmony
* chore: up harmony
* chore: up harmony
* chore: up themes
* chore: up harmony
* remove extra class
* refactor: move these to core from harmony
* chore: up widgets
* chore: up widgets
* height auto
* fix: closes #11238
* dont focus inputs, annoying on mobile
* fix: dont focus twice, only focus on chat input on desktop
dont wrap widget footer in row
* chore: up harmony
* chore: up harmony
* update chat window
* chore: up themes
* fix cache buster for skins
* chat fixes
* chore: up harmony
* chore: up composer
* refactor: change hook logs to debug
* fix: scroll to post right after adding to dom
* fix: hash scrolling and highlighting correct post
* test: re-enable read API schema tests
* fix: add back schema changes for 179faa2270f2ad955dcc4a7b04755acce59e6ffd and c3920ccb10d8ead2dcd9914bb1784bed3f6adfd4
* fix: schema changes from 488f0978a4aa1ca1e4d2a1f2e8c7ef7a681f2f27
* fix: schema changes for f4cf482a874701ce80c0f306c49d8788cec66f87
* fix: schema update for be6bbabd0e2551fbe9571dcf3ee40ad721764543
* fix: schema changes for 69c96078ea78ee2c45885a90a6f6a59f9042a33c
* fix: schema changes for d1364c313021e48a879a818b24947e1457c062f7
* fix: schema changes for 84ff1152f7552dd866e25a90972d970b9861107e
* fix: schema changes for b860c2605c209e0650ef98f4c80d842ea23a51ce
* fix: schema changes for 23cb67a1126481848fac39aafd1e253441e76d7f
* fix: schema changes for b916e42f400dac8aa51670b15e439f87f0eb8939
* fix: schema change for a9bbb586fcb3a1c61b5fb69052236e78cdf7d743
* fix: schema changes for 4b738c8cd36c936a1dbe2bb900c694bf6c5520ec
* fix: schema changes for 58b5781cea9acb129e6604a82ab5a5bfc0d8394d
* fix: schema changes for 794bf01b21709c4be06584d576d706b3d6342057
* fix: schema changes for 80ea12c1c1963f5b39fb64841e4f3c8da3c87af2, e368feef51e0766f119c9710fb4db8f64724725c, and 52ead114bec961c62fa2eb0786540e229f6e4873
* fix: composer-default object in config?
* fix: schema changes for 9acdc6808c070555352951c651921df181b10993 and 093093420027999df3c67bf0ea6024f6dbf81d2d
* fix: schema changes for c0a52924f1f7ef8caeaacda67363ac269b56042c
* fix: schema change for aba420a3f3b774e949c2539c73f3dc0e1ae79a38, move loggedInUser to optional props
* fix: schema changes for 8c67031609da30d788561459f8bb76e9a69253de
* fix: schema changes for 27e53b42f3ce48fa61d3754375715cd41ffe808d
* fix: schema changes for 28359665187b0a3b9ec6226dca1234ebdbd725a5
* fix: breaking test for email confirmation API call
* fix: schema changes for refactored search page
* fix: schema changes for user object
* fix: schema changes for 9f531f957e08eabb4bae844ddd67bde14d9b59f0
* fix: schema changes for c4042c70decd628e5b880bd109515b47e4e16164 and 23175110a29640e6fa052db1079bfedb34a61055
* fix: schema changes for 9b3616b10392e247974eb0c1e6225a1582bf6c69
* fix: schema changes for 5afd5de07d42fd33f039a6f85ded3b4992200e5a
* fix: schema change for 1d7baf12171cffbd3af8914bef4e6297d1160d49
* fix: schema changes for 57bfb37c55a839662144e684875003ab52315ecc and be6bbabd0e2551fbe9571dcf3ee40ad721764543
* fix: schema changes for 6e86b4afa20d662af8b9f1c07518df2d8c258105 and 3efad2e13b7319eb9a1f4fda7af047be43ebc11f and 68f66223e73a72f378f193c83a9b5546bede2cda
* fix: allowing optional qs prop in pagination keys (not sure why this didn't break before)
* fix: re-login on email change
* fix: schema changes for c926358d734a2fa410de87f4e4a91744215fc14a
* fix: schema changes for 388a8270c9882892bad5c8141f65da8d59eac0fd
* fix: schema change for 2658bcc821c22e137a6eeb9bb74098856a642eaf
* fix: no need to call account middlewares for chats routes
* fix: schema changes for 71743affc3e58dc85d4ffa15ce043d4d9ddd3d67
* fix: final schema changes
* test: support for anyOf and oneOf
* fix: check thumb
* dont scroll to top on back press
* remove group log
* fix: add top margin to merged and deleted alerts
* chore: up widgets
* fix: improve fix-lists mixin
* chore: up harmony/composer
* feat: allow hiding quicksearch results during search
* dont record searches made by composer
* chore: up 54
* chore: up spam be gone
* feat: add prev/next page and page count into mobile paginator
* chore: up harmony
* chore: up harmony
* use old style for IS
* fix: hide entire toolbar row if no posts or not singlePost
* fix: updated messaging for post-queue template, #11206
* fix: btn-sm on post queue back button
* fix: bump harmony, closes #11206
* fix: remove unused alert module import
* fix: bump harmony
* fix: bump harmony
* chore: up harmony
* refactor: IS scrolltop
* fix: update users:search-user-for-chat source string
* feat: support for mark-read toggle on chats dropdown and recent chats list
* feat: api v3 calls to mark chat read/unread
* feat: send event:chats.mark socket event on mark read or unread
* refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling
* docs: openapi schema updates for chat marking
* fix: allow unread state toggling in chats dropdown too
* fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread
* fix: debug log
* refactor: move userSearch filter to a module
* feat(routes): allow remounting /categories (#11230)
* feat: send flags count to frontend on flags list page
* refactor: filter form client-side js to extract out some logic
* fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden
* fix: use userFilter module for assignee, reporterId, targetUid
* fix(openapi): schema changes for updated flags page
* fix: dont allow adding duplicates to userFilter
* use same var
* remove log
* fix: closes #11282
* feat: lang key for x-topics
* chore: up harmony
* chore: up emoji
* chore: up harmony
* fix: update userFilter to allow new option `selectedBlock`
* fix: wrong block name passed to userFilter
* fix: https://github.com/NodeBB/NodeBB/issues/11283
* fix: chats, allow multiple dropdowns like in harmony
* chore: up harmony
* refactor: flag note adding/editing, closes #11285
* fix: remove old prepareEdit logic
* chore: add caveat about hacky code block in userFilter module
* fix: placeholders for userFilter module
* refactor: navigator so it works with multiple thumbs/navigators
* chore: up harmony
* fix: closes #11287, destroy quick reply autocomplete
on navigation
* fix: filter disabled categories on user categories page count
* chore: up harmony
* docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying
* fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests
* fix: tweak table order in ACP dash searches
* fix: only invoke navigator click drag on left mouse button
* feat: add back unread indicator to navigator
* clear bookmark on mark unread
* fix: navigator crash on ajaxify
* better thumb top calculation
* fix: reset user bookmark when topic is marked unread
* Revert "fix: reset user bookmark when topic is marked unread"
This reverts commit 9bcd85c2c6848c3d325d32027261809da6e11c9e.
* fix: update unread indicator on scroll, add unread count
* chore: bump harmony
* fix: crash on navigator unread update when backing out of a topic
* fix: closes #11183
* fix: update topics:recent zset when rescheduling a topic
* fix: dupe quote button, increase delay, hide immediately on empty selection
* fix: navigator not showing up on first load
* refactor: remove glance
assorted fixes to navigator
dont reduce remaning count if user scrolls down and up quickly
only call topic.navigatorCallback when index changes
* more sanity checks for bookmark
dont allow setting bookmark higher than topic postcount
* closes #11218, :train:
* Revert "fix: update topics:recent zset when rescheduling a topic"
This reverts commit 737973cca9e94b6cb3867492a09e1e0b1af391d5.
* fix: #11306, show proper error if queued post doesn't exist
was showing no-privileges if someone else accepted the post
* https://github.com/NodeBB/NodeBB/issues/11307
dont use li
* chore: up harmony
* chore: bump version string
* fix: copy paste fail
* feat: closes #7382, tag filtering
add client side support for filtering by tags on /category, /recent and /unread
* chore: up harmony
* chore: up harmony
* Revert "fix: add back req.query fallback for backwards compatibility" [breaking]
This reverts commit cf6cc2c454dc35c330393c62ee8ce67b42d8eefb.
This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x
This is a breaking change.
* fix: pass csrf token in form data, re: NodeBB/NodeBB#11309
* chore: up deps
* fix: tests, use x-csrf-token query param removed
* test: fix csrf_token
* lint: remove unused
* feat: add itemprop="image" to avatar helper
* fix: get chat upload button in chat modal
* breaking: remove deprecated socket.io methods
* test: update messaging tests to not use sockets
* fix: parent post links
* fix: prevent post tooltip if mouse leaves before data/tpl is loaded
* chore: up harmony
* chore: up harmony
* chore: up harmony
* chore: up harmony
* fix: nested replies indices
* fix(deps): bump 2factor
* feat: add loggedIn user to all api routes
* chore: up themes
* refactor: audit admin v3 write api routes as per #11321
* refactor: audit category v3 write api routes as per #11321 [breaking]
docs: fix open api spec for #11321
* refactor: audit chat v3 write api routes as per #11321
* refactor: audit files v3 write api routes as per #11321
* refactor: audit flags v3 write api routes as per #11321
* refactor: audit posts v3 write api routes as per #11321
* refactor: audit topics v3 write api routes as per #11321
* refactor: audit users v3 write api routes as per #11321
* fix: lang string
* remove min height
* fix: empty topic/labels taking up space
* fix: tag filtering when changing filter to watched topics
or changing popular time limit to month
* chore: up harmony
* fix: closes #11354, show no post error if queued post already accepted/rejected
* test: #11354
* test: #11354
* fix(deps): bump 2factor
* fix: #11357 clear cache on thumb remove
* fix: thumb remove on windows, closes #11357
* test: openapi for thumbs
* test: fix openapi
---------
Co-authored-by: Julian Lam <[email protected] >
Co-authored-by: Opliko <[email protected] >
2 years ago
assert . equal ( err . message , '[[error:no-post]]' ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should accept queued posts and submit' , ( done ) => {
let ids ;
async . waterfall ( [
function ( next ) {
db . getSortedSetRange ( 'post:queue' , 0 , - 1 , next ) ;
} ,
function ( _ids , next ) {
ids = _ids ;
socketPosts . accept ( { uid : globalModUid } , { id : ids [ 0 ] } , next ) ;
} ,
function ( next ) {
socketPosts . accept ( { uid : globalModUid } , { id : ids [ 1 ] } , next ) ;
} ,
] , done ) ;
} ) ;
it ( 'should not crash if id does not exist' , ( done ) => {
socketPosts . reject ( { uid : globalModUid } , { id : '123123123' } , ( err ) => {
Bootstrap5 (#10894)
* chore: up deps
* chore: up composer
* fix(deps): bump 2factor to v7
* chore: up harmony
* chore: up harmony
* fix: missing await
* feat: allow middlewares to pass in template values via res.locals
* feat: buildAccountData middleware automatically added ot all account routes
* fix: properly allow values in res.locals.templateValues to be added to the template data
* refactor: user/blocks
* refactor(accounts): categories and consent
* feat: automatically 404 if exposeUid or exposeGroupName come up empty
* refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now
* fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization
* fix: move reputation removal check to accountHelpers method
* test: skip i18n tests if ref branch when present is not develop
* fix(deps): bump theme versions
* fix(deps): bump ntfy and 2factor
* chore: up harmony
* fix: add missing return
* fix: #11191, only focus on search input on md environments and up
* feat: allow file uploads on mobile chat
closes https://github.com/NodeBB/NodeBB/issues/11217
* chore: up themes
* chore: add lang string
* fix(deps): bump ntfy to 1.0.15
* refactor: use new if/each syntax
* chore: up composer
* fix: regression from user helper refactor
* chore: up harmony
* chore: up composer
* chore: up harmony
* chore: up harmony
* chore: up harmony
* chore: fix composer version
* feat: add increment helper
* chore: up harmony
* fix: #11228 no timestamps in future :hourglass:
* chore: up harmony
* check config.theme as well
fire action:posts.loaded after processing dom
* chore: up harmony
* chore: up harmony
* chore: up harmony
* chore: up themes
* chore: up harmony
* remove extra class
* refactor: move these to core from harmony
* chore: up widgets
* chore: up widgets
* height auto
* fix: closes #11238
* dont focus inputs, annoying on mobile
* fix: dont focus twice, only focus on chat input on desktop
dont wrap widget footer in row
* chore: up harmony
* chore: up harmony
* update chat window
* chore: up themes
* fix cache buster for skins
* chat fixes
* chore: up harmony
* chore: up composer
* refactor: change hook logs to debug
* fix: scroll to post right after adding to dom
* fix: hash scrolling and highlighting correct post
* test: re-enable read API schema tests
* fix: add back schema changes for 179faa2270f2ad955dcc4a7b04755acce59e6ffd and c3920ccb10d8ead2dcd9914bb1784bed3f6adfd4
* fix: schema changes from 488f0978a4aa1ca1e4d2a1f2e8c7ef7a681f2f27
* fix: schema changes for f4cf482a874701ce80c0f306c49d8788cec66f87
* fix: schema update for be6bbabd0e2551fbe9571dcf3ee40ad721764543
* fix: schema changes for 69c96078ea78ee2c45885a90a6f6a59f9042a33c
* fix: schema changes for d1364c313021e48a879a818b24947e1457c062f7
* fix: schema changes for 84ff1152f7552dd866e25a90972d970b9861107e
* fix: schema changes for b860c2605c209e0650ef98f4c80d842ea23a51ce
* fix: schema changes for 23cb67a1126481848fac39aafd1e253441e76d7f
* fix: schema changes for b916e42f400dac8aa51670b15e439f87f0eb8939
* fix: schema change for a9bbb586fcb3a1c61b5fb69052236e78cdf7d743
* fix: schema changes for 4b738c8cd36c936a1dbe2bb900c694bf6c5520ec
* fix: schema changes for 58b5781cea9acb129e6604a82ab5a5bfc0d8394d
* fix: schema changes for 794bf01b21709c4be06584d576d706b3d6342057
* fix: schema changes for 80ea12c1c1963f5b39fb64841e4f3c8da3c87af2, e368feef51e0766f119c9710fb4db8f64724725c, and 52ead114bec961c62fa2eb0786540e229f6e4873
* fix: composer-default object in config?
* fix: schema changes for 9acdc6808c070555352951c651921df181b10993 and 093093420027999df3c67bf0ea6024f6dbf81d2d
* fix: schema changes for c0a52924f1f7ef8caeaacda67363ac269b56042c
* fix: schema change for aba420a3f3b774e949c2539c73f3dc0e1ae79a38, move loggedInUser to optional props
* fix: schema changes for 8c67031609da30d788561459f8bb76e9a69253de
* fix: schema changes for 27e53b42f3ce48fa61d3754375715cd41ffe808d
* fix: schema changes for 28359665187b0a3b9ec6226dca1234ebdbd725a5
* fix: breaking test for email confirmation API call
* fix: schema changes for refactored search page
* fix: schema changes for user object
* fix: schema changes for 9f531f957e08eabb4bae844ddd67bde14d9b59f0
* fix: schema changes for c4042c70decd628e5b880bd109515b47e4e16164 and 23175110a29640e6fa052db1079bfedb34a61055
* fix: schema changes for 9b3616b10392e247974eb0c1e6225a1582bf6c69
* fix: schema changes for 5afd5de07d42fd33f039a6f85ded3b4992200e5a
* fix: schema change for 1d7baf12171cffbd3af8914bef4e6297d1160d49
* fix: schema changes for 57bfb37c55a839662144e684875003ab52315ecc and be6bbabd0e2551fbe9571dcf3ee40ad721764543
* fix: schema changes for 6e86b4afa20d662af8b9f1c07518df2d8c258105 and 3efad2e13b7319eb9a1f4fda7af047be43ebc11f and 68f66223e73a72f378f193c83a9b5546bede2cda
* fix: allowing optional qs prop in pagination keys (not sure why this didn't break before)
* fix: re-login on email change
* fix: schema changes for c926358d734a2fa410de87f4e4a91744215fc14a
* fix: schema changes for 388a8270c9882892bad5c8141f65da8d59eac0fd
* fix: schema change for 2658bcc821c22e137a6eeb9bb74098856a642eaf
* fix: no need to call account middlewares for chats routes
* fix: schema changes for 71743affc3e58dc85d4ffa15ce043d4d9ddd3d67
* fix: final schema changes
* test: support for anyOf and oneOf
* fix: check thumb
* dont scroll to top on back press
* remove group log
* fix: add top margin to merged and deleted alerts
* chore: up widgets
* fix: improve fix-lists mixin
* chore: up harmony/composer
* feat: allow hiding quicksearch results during search
* dont record searches made by composer
* chore: up 54
* chore: up spam be gone
* feat: add prev/next page and page count into mobile paginator
* chore: up harmony
* chore: up harmony
* use old style for IS
* fix: hide entire toolbar row if no posts or not singlePost
* fix: updated messaging for post-queue template, #11206
* fix: btn-sm on post queue back button
* fix: bump harmony, closes #11206
* fix: remove unused alert module import
* fix: bump harmony
* fix: bump harmony
* chore: up harmony
* refactor: IS scrolltop
* fix: update users:search-user-for-chat source string
* feat: support for mark-read toggle on chats dropdown and recent chats list
* feat: api v3 calls to mark chat read/unread
* feat: send event:chats.mark socket event on mark read or unread
* refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling
* docs: openapi schema updates for chat marking
* fix: allow unread state toggling in chats dropdown too
* fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread
* fix: debug log
* refactor: move userSearch filter to a module
* feat(routes): allow remounting /categories (#11230)
* feat: send flags count to frontend on flags list page
* refactor: filter form client-side js to extract out some logic
* fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden
* fix: use userFilter module for assignee, reporterId, targetUid
* fix(openapi): schema changes for updated flags page
* fix: dont allow adding duplicates to userFilter
* use same var
* remove log
* fix: closes #11282
* feat: lang key for x-topics
* chore: up harmony
* chore: up emoji
* chore: up harmony
* fix: update userFilter to allow new option `selectedBlock`
* fix: wrong block name passed to userFilter
* fix: https://github.com/NodeBB/NodeBB/issues/11283
* fix: chats, allow multiple dropdowns like in harmony
* chore: up harmony
* refactor: flag note adding/editing, closes #11285
* fix: remove old prepareEdit logic
* chore: add caveat about hacky code block in userFilter module
* fix: placeholders for userFilter module
* refactor: navigator so it works with multiple thumbs/navigators
* chore: up harmony
* fix: closes #11287, destroy quick reply autocomplete
on navigation
* fix: filter disabled categories on user categories page count
* chore: up harmony
* docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying
* fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests
* fix: tweak table order in ACP dash searches
* fix: only invoke navigator click drag on left mouse button
* feat: add back unread indicator to navigator
* clear bookmark on mark unread
* fix: navigator crash on ajaxify
* better thumb top calculation
* fix: reset user bookmark when topic is marked unread
* Revert "fix: reset user bookmark when topic is marked unread"
This reverts commit 9bcd85c2c6848c3d325d32027261809da6e11c9e.
* fix: update unread indicator on scroll, add unread count
* chore: bump harmony
* fix: crash on navigator unread update when backing out of a topic
* fix: closes #11183
* fix: update topics:recent zset when rescheduling a topic
* fix: dupe quote button, increase delay, hide immediately on empty selection
* fix: navigator not showing up on first load
* refactor: remove glance
assorted fixes to navigator
dont reduce remaning count if user scrolls down and up quickly
only call topic.navigatorCallback when index changes
* more sanity checks for bookmark
dont allow setting bookmark higher than topic postcount
* closes #11218, :train:
* Revert "fix: update topics:recent zset when rescheduling a topic"
This reverts commit 737973cca9e94b6cb3867492a09e1e0b1af391d5.
* fix: #11306, show proper error if queued post doesn't exist
was showing no-privileges if someone else accepted the post
* https://github.com/NodeBB/NodeBB/issues/11307
dont use li
* chore: up harmony
* chore: bump version string
* fix: copy paste fail
* feat: closes #7382, tag filtering
add client side support for filtering by tags on /category, /recent and /unread
* chore: up harmony
* chore: up harmony
* Revert "fix: add back req.query fallback for backwards compatibility" [breaking]
This reverts commit cf6cc2c454dc35c330393c62ee8ce67b42d8eefb.
This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x
This is a breaking change.
* fix: pass csrf token in form data, re: NodeBB/NodeBB#11309
* chore: up deps
* fix: tests, use x-csrf-token query param removed
* test: fix csrf_token
* lint: remove unused
* feat: add itemprop="image" to avatar helper
* fix: get chat upload button in chat modal
* breaking: remove deprecated socket.io methods
* test: update messaging tests to not use sockets
* fix: parent post links
* fix: prevent post tooltip if mouse leaves before data/tpl is loaded
* chore: up harmony
* chore: up harmony
* chore: up harmony
* chore: up harmony
* fix: nested replies indices
* fix(deps): bump 2factor
* feat: add loggedIn user to all api routes
* chore: up themes
* refactor: audit admin v3 write api routes as per #11321
* refactor: audit category v3 write api routes as per #11321 [breaking]
docs: fix open api spec for #11321
* refactor: audit chat v3 write api routes as per #11321
* refactor: audit files v3 write api routes as per #11321
* refactor: audit flags v3 write api routes as per #11321
* refactor: audit posts v3 write api routes as per #11321
* refactor: audit topics v3 write api routes as per #11321
* refactor: audit users v3 write api routes as per #11321
* fix: lang string
* remove min height
* fix: empty topic/labels taking up space
* fix: tag filtering when changing filter to watched topics
or changing popular time limit to month
* chore: up harmony
* fix: closes #11354, show no post error if queued post already accepted/rejected
* test: #11354
* test: #11354
* fix(deps): bump 2factor
* fix: #11357 clear cache on thumb remove
* fix: thumb remove on windows, closes #11357
* test: openapi for thumbs
* test: fix openapi
---------
Co-authored-by: Julian Lam <[email protected] >
Co-authored-by: Opliko <[email protected] >
2 years ago
assert . equal ( err . message , '[[error:no-post]]' ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should bypass post queue if user is in exempt group' , async ( ) => {
const oldValue = meta . config . groupsExemptFromPostQueue ;
meta . config . groupsExemptFromPostQueue = [ 'registered-users' ] ;
const uid = await user . create ( { username : 'mergeexemptuser' } ) ;
const result = await apiTopics . create ( { uid : uid , emit : ( ) => { } } , { title : 'should not be queued' , content : 'topic content' , cid : cid } ) ;
assert . strictEqual ( result . title , 'should not be queued' ) ;
meta . config . groupsExemptFromPostQueue = oldValue ;
} ) ;
it ( 'should update queued post\'s topic if target topic is merged' , async ( ) => {
const uid = await user . create ( { username : 'mergetestsuser' } ) ;
const result1 = await apiTopics . create ( { uid : globalModUid } , { title : 'topic A' , content : 'topic A content' , cid : cid } ) ;
const result2 = await apiTopics . create ( { uid : globalModUid } , { title : 'topic B' , content : 'topic B content' , cid : cid } ) ;
const result = await apiTopics . reply ( { uid : uid } , { content : 'the moved queued post' , tid : result1 . tid } ) ;
await topics . merge ( [
result1 . tid , result2 . tid ,
] , globalModUid , { mainTid : result2 . tid } ) ;
let postData = await posts . getQueuedPosts ( ) ;
postData = postData . filter ( p => parseInt ( p . data . tid , 10 ) === parseInt ( result2 . tid , 10 ) ) ;
assert . strictEqual ( postData . length , 1 ) ;
assert . strictEqual ( postData [ 0 ] . data . content , 'the moved queued post' ) ;
assert . strictEqual ( postData [ 0 ] . data . tid , result2 . tid ) ;
} ) ;
} ) ;
describe ( 'Topic Backlinks' , ( ) => {
let tid1 ;
before ( async ( ) => {
tid1 = await topics . post ( {
uid : 1 ,
cid ,
title : 'Topic backlink testing - topic 1' ,
content : 'Some text here for the OP' ,
} ) ;
tid1 = tid1 . topicData . tid ;
} ) ;
describe ( '.syncBacklinks()' , ( ) => {
it ( 'should error on invalid data' , async ( ) => {
try {
await topics . syncBacklinks ( ) ;
} catch ( e ) {
assert ( e ) ;
assert . strictEqual ( e . message , '[[error:invalid-data]]' ) ;
}
} ) ;
it ( 'should do nothing if the post does not contain a link to a topic' , async ( ) => {
const backlinks = await topics . syncBacklinks ( {
content : 'This is a post\'s content' ,
} ) ;
assert . strictEqual ( backlinks , 0 ) ;
} ) ;
it ( 'should create a backlink if it detects a topic link in a post' , async ( ) => {
const count = await topics . syncBacklinks ( {
pid : 2 ,
content : ` This is a link to [topic 1]( ${ nconf . get ( 'url' ) } /topic/1/abcdef) ` ,
} ) ;
const events = await topics . events . get ( 1 , 1 ) ;
const backlinks = await db . getSortedSetMembers ( 'pid:2:backlinks' ) ;
assert . strictEqual ( count , 1 ) ;
assert ( events ) ;
assert . strictEqual ( events . length , 1 ) ;
assert ( backlinks ) ;
assert ( backlinks . includes ( '1' ) ) ;
} ) ;
it ( 'should remove the backlink (but keep the event) if the post no longer contains a link to a topic' , async ( ) => {
const count = await topics . syncBacklinks ( {
pid : 2 ,
content : 'This is a link to [nothing](http://example.org)' ,
} ) ;
const events = await topics . events . get ( 1 , 1 ) ;
const backlinks = await db . getSortedSetMembers ( 'pid:2:backlinks' ) ;
assert . strictEqual ( count , 0 ) ;
assert ( events ) ;
assert . strictEqual ( events . length , 1 ) ;
assert ( backlinks ) ;
assert . strictEqual ( backlinks . length , 0 ) ;
} ) ;
it ( 'should not detect backlinks if they are in quotes' , async ( ) => {
const content = `
@ baris said in [ ok testing backlinks ] ( / p o s t / 3 2 1 4 5 ) :
> here is a back link to a topic
>
>
> This is a link to [ topic 1 ] ( $ { nconf . get ( 'url' ) } / topic / 1 / abcdef
This should not generate backlink
` ;
const count = await topics . syncBacklinks ( {
pid : 2 ,
content : content ,
} ) ;
const backlinks = await db . getSortedSetMembers ( 'pid:2:backlinks' ) ;
assert . strictEqual ( count , 0 ) ;
assert ( backlinks ) ;
assert . strictEqual ( backlinks . length , 0 ) ;
} ) ;
} ) ;
describe ( 'integration tests' , ( ) => {
it ( 'should create a topic event in the referenced topic' , async ( ) => {
const topic = await topics . post ( {
uid : 1 ,
cid ,
title : 'Topic backlink testing - topic 2' ,
content : ` Some text here for the OP – ${ nconf . get ( 'url' ) } /topic/ ${ tid1 } ` ,
} ) ;
const events = await topics . events . get ( tid1 , 1 ) ;
assert ( events ) ;
assert . strictEqual ( events . length , 1 ) ;
assert . strictEqual ( events [ 0 ] . type , 'backlink' ) ;
assert . strictEqual ( parseInt ( events [ 0 ] . uid , 10 ) , 1 ) ;
assert . strictEqual ( events [ 0 ] . href , ` /post/ ${ topic . postData . pid } ` ) ;
} ) ;
it ( 'should not create a topic event if referenced topic is the same as current topic' , async ( ) => {
await topics . reply ( {
uid : 1 ,
tid : tid1 ,
content : ` Referencing itself – ${ nconf . get ( 'url' ) } /topic/ ${ tid1 } ` ,
} ) ;
const events = await topics . events . get ( tid1 , 1 ) ;
assert ( events ) ;
assert . strictEqual ( events . length , 1 ) ; // should still equal 1
} ) ;
it ( 'should not show backlink events if the feature is disabled' , async ( ) => {
meta . config . topicBacklinks = 0 ;
await topics . post ( {
uid : 1 ,
cid ,
title : 'Topic backlink testing - topic 3' ,
content : ` Some text here for the OP – ${ nconf . get ( 'url' ) } /topic/ ${ tid1 } ` ,
} ) ;
const events = await topics . events . get ( tid1 , 1 ) ;
assert ( events ) ;
assert . strictEqual ( events . length , 0 ) ;
} ) ;
} ) ;
} ) ;
} ) ;
describe ( 'Posts\'' , async ( ) => {
let files ;
before ( async ( ) => {
files = await file . walk ( path . resolve ( _ _dirname , './posts' ) ) ;
} ) ;
it ( 'subfolder tests' , ( ) => {
files . forEach ( ( filePath ) => {
require ( filePath ) ;
} ) ;
} ) ;
} ) ;