diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4f55356b8b3cf2094f31d6c9367dc14d2e2cf55c..527bc7b615495d8f9bab081a5d83669dc19b6c0e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -30,13 +30,13 @@ class ApplicationController < ActionController::Base devise_parameter_sanitizer.permit(:account_update, keys: [:username, :profile, :website, :twitter]) end - def not_found + def not_found(**add) respond_to do |format| format.html do render 'errors/not_found', layout: 'without_sidebar', status: :not_found end format.json do - render json: { status: 'failed', errors: ['not_found'] }, status: :not_found + render json: { status: 'failed', success: false, errors: ['not_found'] }.merge(add), status: :not_found end end false @@ -49,7 +49,7 @@ class ApplicationController < ActionController::Base render 'errors/not_found', layout: 'without_sidebar', status: :not_found end format.json do - render json: { status: 'failed', errors: ['not_found'] }, status: :not_found + render json: { status: 'failed', success: false, errors: ['not_found'] }, status: :not_found end end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 2cf943f3a5f950a2112d04d5476bb9507f334fb4..c5c6ee388f94cf6e1883407e33810e8c3813a712 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -384,17 +384,19 @@ class PostsController < ApplicationController @post.update locked: true, locked_by: current_user, locked_at: DateTime.now, locked_until: end_date - render json: { success: true } + render json: { status: 'success', success: true } end def unlock - return not_found unless current_user.privilege? 'flag_curate' - return not_found unless @post.locked? - return not_found if @post.locked_until.nil? && !current_user.is_moderator + return not_found(errors: ['no_privilege']) unless current_user.privilege? 'flag_curate' + return not_found(errors: ['not_locked']) unless @post.locked? + if @post.locked_by.is_moderator && !current_user.is_moderator + return not_found(errors: ['locked_by_mod']) + end @post.update locked: false, locked_by: nil, locked_at: nil, locked_until: nil - render json: { success: true } + render json: { status: 'success', success: true } end def feature @@ -408,9 +410,9 @@ class PostsController < ApplicationController attr = @link.attributes_print AuditLog.moderator_audit(event_type: 'pinned_link_create', related: @link, user: current_user, - comment: "<<PinnedLink #{attr}>>\n(using moderator tools on post)") + comment: "<<PinnedLink #{attr}>>\n(using moderator tools on post)") flash[:success] = 'Post has been featured. Due to caching, it may take some time until the changes apply.' - render json: { success: true } + render json: { status: 'success', success: true } end def save_draft @@ -420,14 +422,14 @@ class PostsController < ApplicationController RequestContext.redis.set saved_at, DateTime.now.iso8601 RequestContext.redis.expire key, 86_400 * 7 RequestContext.redis.expire saved_at, 86_400 * 7 - render json: { success: true, key: key } + render json: { status: 'success', success: true, key: key } end def delete_draft key = "saved_post.#{current_user.id}.#{params[:path]}" saved_at = "saved_post_at.#{current_user.id}.#{params[:path]}" RequestContext.redis.del key, saved_at - render json: { success: true } + render json: { status: 'success', success: true } end private diff --git a/test/controllers/posts_controller_test.rb b/test/controllers/posts_controller_test.rb index e476ce678702b94e136435679c8da82f98f84a31..f149939b124ca0194b3e937d913ecf0fa282ebe2 100644 --- a/test/controllers/posts_controller_test.rb +++ b/test/controllers/posts_controller_test.rb @@ -670,4 +670,135 @@ class PostsControllerTest < ActionController::TestCase assert assigns(:post).comments_disabled assert assigns(:post).comments.all?(&:deleted?) end + + # Lock + + test 'can lock post' do + sign_in users(:deleter) + post :lock, params: { id: posts(:question_one).id, format: :json } + assert_response 200 + assert_not_nil assigns(:post) + assert assigns(:post).locked_until <= 7.days.from_now + assert assigns(:post).locked_until >= 7.days.from_now - 1.minute + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'success', JSON.parse(response.body)['status'] + end + + test 'lock requires authentication' do + post :lock, params: { id: posts(:question_one).id } + assert_response 302 + assert_redirected_to new_user_session_path + end + + test 'unprivileged user cannot lock' do + sign_in users(:standard_user) + post :lock, params: { id: posts(:question_one).id, format: :json } + assert_response 404 + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'failed', JSON.parse(response.body)['status'] + end + + test 'cannot lock locked post' do + sign_in users(:deleter) + post :lock, params: { id: posts(:locked).id, format: :json } + assert_response 404 + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'failed', JSON.parse(response.body)['status'] + end + + test 'cannot lock longer than 30 days' do + sign_in users(:deleter) + post :lock, params: { id: posts(:question_one).id, length: 60, format: :json } + assert_response 200 + assert_not_nil assigns(:post) + assert assigns(:post).locked_until <= 30.days.from_now + assert assigns(:post).locked_until >= 30.days.from_now - 1.minute + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'success', JSON.parse(response.body)['status'] + end + + test 'moderator can lock longer than 30 days' do + sign_in users(:moderator) + post :lock, params: { id: posts(:question_one).id, length: 60, format: :json } + assert_response 200 + assert_not_nil assigns(:post) + assert assigns(:post).locked_until <= 60.days.from_now + assert assigns(:post).locked_until >= 60.days.from_now - 1.minute + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'success', JSON.parse(response.body)['status'] + end + + test 'moderator can lock indefinitely' do + sign_in users(:moderator) + post :lock, params: { id: posts(:question_one).id, format: :json } + assert_response 200 + assert_not_nil assigns(:post) + assert_nil assigns(:post).locked_until + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'success', JSON.parse(response.body)['status'] + end + + # Unlock + + test 'can unlock post' do + sign_in users(:deleter) + posts(:locked).update(locked_until: 2.days.from_now) + post :unlock, params: { id: posts(:locked).id, format: :json } + assert_response 200 + assert_not_nil assigns(:post) + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'success', JSON.parse(response.body)['status'] + end + + test 'unlock requires authentication' do + post :unlock, params: { id: posts(:locked).id } + assert_response 302 + assert_redirected_to new_user_session_path + end + + test 'unprivileged user cannot unlock' do + sign_in users(:standard_user) + post :unlock, params: { id: posts(:locked).id, format: :json } + assert_response 404 + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'failed', JSON.parse(response.body)['status'] + end + + test 'cannot unlock unlocked post' do + sign_in users(:deleter) + post :unlock, params: { id: posts(:question_one).id, format: :json } + assert_response 404 + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'failed', JSON.parse(response.body)['status'] + end + + test 'cannot unlock post locked by moderator' do + sign_in users(:deleter) + posts(:locked_mod).update(locked_until: 2.days.from_now) + post :unlock, params: { id: posts(:locked_mod).id, format: :json } + assert_response 404 + assert_nothing_raised do + JSON.parse(response.body) + end + assert_equal 'failed', JSON.parse(response.body)['status'] + assert_equal ['locked_by_mod'], JSON.parse(response.body)['errors'] + end end diff --git a/test/fixtures/posts.yml b/test/fixtures/posts.yml index 25ac1dd327909d03773cbe268cf4e1d3a5eacd28..a207532c46a83281aed5aab8d7ec63713f5b7a81 100644 --- a/test/fixtures/posts.yml +++ b/test/fixtures/posts.yml @@ -127,8 +127,34 @@ locked: - bug score: 0.5 locked: true + locked_by: deleter + locked_at: 2019-01-01T00:00:00.000000Z + locked_until: 2020-01-01T00:00:00.000000Z + user: standard_user + community: sample + category: main + license: cc_by_sa + upvote_count: 0 + downvote_count: 0 + +locked_mod: + post_type: question + title: LM ABCDEF GHIJKL MNOPQR STUVWX YZ + body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ + body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA + tags_cache: + - discussion + - support + - bug + tags: + - discussion + - support + - bug + score: 0.5 + locked: true locked_by: moderator locked_at: 2019-01-01T00:00:00.000000Z + locked_until: 2020-01-01T00:00:00.000000Z user: standard_user community: sample category: main