votes: allow admins to remove post votes.
Allow admins to remove votes on posts. This is for fixing vote abuse. Votes can be removed by going to the vote list on the /post_votes page, or by clicking on a post's score, then using the "Remove" option in the "..." dropdown menu next to the vote. Votes are soft-deleted - they're marked as deleted in the database, but not fully deleted. Removed votes are only visible to admins, not to regular users. When a vote is removed by an admin, it leaves a mod action. Technically it's possible to undelete votes, but there's no UI for it.
This commit is contained in:
@@ -40,7 +40,8 @@ class FavoriteTest < ActiveSupport::TestCase
|
||||
assert_equal(0, @user.reload.favorite_count)
|
||||
assert_equal(0, @p1.reload.fav_count)
|
||||
assert_equal(0, @p1.reload.score)
|
||||
refute(PostVote.positive.exists?(post: @p1, user: @user))
|
||||
refute(PostVote.active.positive.exists?(post: @p1, user: @user))
|
||||
assert(PostVote.deleted.positive.exists?(post: @p1, user: @user))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -75,8 +76,8 @@ class FavoriteTest < ActiveSupport::TestCase
|
||||
assert_equal(1, @user.reload.favorite_count)
|
||||
assert_equal(1, @p1.reload.fav_count)
|
||||
assert_equal(1, @p1.reload.score)
|
||||
assert(PostVote.positive.exists?(post: @p1, user: @user))
|
||||
refute(PostVote.negative.exists?(post: @p1, user: @user))
|
||||
assert(PostVote.active.positive.exists?(post: @p1, user: @user))
|
||||
assert(PostVote.deleted.negative.exists?(post: @p1, user: @user))
|
||||
end
|
||||
|
||||
should "not allow duplicate favorites" do
|
||||
|
||||
@@ -701,12 +701,12 @@ class PostTest < ActiveSupport::TestCase
|
||||
@post.update(tag_string: "aaa fav:self")
|
||||
assert_equal(1, @post.reload.score)
|
||||
assert_equal(1, @post.favorites.where(user: @user).count)
|
||||
assert_equal(1, @post.votes.positive.where(user: @user).count)
|
||||
assert_equal(1, @post.votes.active.positive.where(user: @user).count)
|
||||
|
||||
@post.update(tag_string: "aaa -fav:self")
|
||||
assert_equal(0, @post.reload.score)
|
||||
assert_equal(0, @post.favorites.count)
|
||||
assert_equal(0, @post.votes.positive.where(user: @user).count)
|
||||
assert_equal(0, @post.votes.active.positive.where(user: @user).count)
|
||||
end
|
||||
|
||||
should "not allow banned users to fav" do
|
||||
@@ -1433,11 +1433,12 @@ class PostTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
should "not decrement the post's score for basic users" do
|
||||
should "decrement the post's score for basic users" do
|
||||
@member = FactoryBot.create(:user)
|
||||
|
||||
assert_no_difference("@post.score") { create(:favorite, post: @post, user: @member) }
|
||||
assert_no_difference("@post.score") { Favorite.destroy_by(post: @post, user: @member) }
|
||||
assert_difference("@post.reload.score", -1) do
|
||||
Favorite.destroy_by(post: @post, user: @user)
|
||||
end
|
||||
end
|
||||
|
||||
should "not decrement the user's favorite_count if the user did not favorite the post" do
|
||||
@@ -1565,7 +1566,7 @@ class PostTest < ActiveSupport::TestCase
|
||||
post.vote!(1, user)
|
||||
|
||||
assert_equal(1, post.reload.score)
|
||||
assert_equal(1, post.votes.count)
|
||||
assert_equal(1, post.votes.active.count)
|
||||
end
|
||||
|
||||
should "allow undoing of votes" do
|
||||
@@ -1578,31 +1579,33 @@ class PostTest < ActiveSupport::TestCase
|
||||
assert_equal(1, post.score)
|
||||
assert_equal(1, post.up_score)
|
||||
assert_equal(0, post.down_score)
|
||||
assert_equal(1, post.votes.positive.count)
|
||||
assert_equal(1, post.votes.active.positive.count)
|
||||
|
||||
post.unvote!(user)
|
||||
post.votes.last.soft_delete!
|
||||
post.reload
|
||||
assert_equal(0, post.score)
|
||||
assert_equal(0, post.up_score)
|
||||
assert_equal(0, post.down_score)
|
||||
assert_equal(0, post.votes.count)
|
||||
assert_equal(0, post.votes.active.count)
|
||||
|
||||
post.vote!(-1, user)
|
||||
assert_equal(-1, post.score)
|
||||
assert_equal(0, post.up_score)
|
||||
assert_equal(-1, post.down_score)
|
||||
assert_equal(1, post.votes.negative.count)
|
||||
assert_equal(1, post.votes.active.negative.count)
|
||||
|
||||
post.unvote!(user)
|
||||
post.votes.last.soft_delete!
|
||||
post.reload
|
||||
assert_equal(0, post.score)
|
||||
assert_equal(0, post.up_score)
|
||||
assert_equal(0, post.down_score)
|
||||
assert_equal(0, post.votes.count)
|
||||
assert_equal(0, post.votes.active.count)
|
||||
|
||||
post.vote!(1, user)
|
||||
assert_equal(1, post.score)
|
||||
assert_equal(1, post.up_score)
|
||||
assert_equal(0, post.down_score)
|
||||
assert_equal(1, post.votes.positive.count)
|
||||
assert_equal(1, post.votes.active.positive.count)
|
||||
|
||||
post.reload
|
||||
assert_equal(1, post.score)
|
||||
|
||||
@@ -9,7 +9,6 @@ class PostVoteTest < ActiveSupport::TestCase
|
||||
context "during validation" do
|
||||
subject { build(:post_vote, post: @post) }
|
||||
|
||||
should validate_uniqueness_of(:user_id).scoped_to(:post_id).with_message("have already voted for this post")
|
||||
should validate_inclusion_of(:score).in_array([-1, 1]).with_message("must be 1 or -1")
|
||||
end
|
||||
|
||||
@@ -21,7 +20,20 @@ class PostVoteTest < ActiveSupport::TestCase
|
||||
assert_equal(1, @post.reload.score)
|
||||
assert_equal(1, @post.up_score)
|
||||
assert_equal(0, @post.down_score)
|
||||
assert_equal(1, @post.votes.positive.count)
|
||||
assert_equal(1, @post.votes.active.positive.count)
|
||||
end
|
||||
|
||||
should "soft delete other votes" do
|
||||
@user = create(:user)
|
||||
vote1 = create(:post_vote, post: @post, user: @user, score: -1)
|
||||
vote2 = create(:post_vote, post: @post, user: @user, score: 1)
|
||||
|
||||
assert_equal(1, @post.reload.score)
|
||||
assert_equal(1, @post.up_score)
|
||||
assert_equal(0, @post.down_score)
|
||||
assert_equal(1, @post.votes.active.positive.count)
|
||||
assert_equal(0, @post.votes.active.negative.count)
|
||||
assert_equal(true, vote1.reload.is_deleted?)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,25 +44,40 @@ class PostVoteTest < ActiveSupport::TestCase
|
||||
assert_equal(-1, @post.reload.score)
|
||||
assert_equal(0, @post.up_score)
|
||||
assert_equal(-1, @post.down_score)
|
||||
assert_equal(1, @post.votes.negative.count)
|
||||
assert_equal(1, @post.votes.active.negative.count)
|
||||
end
|
||||
|
||||
should "soft delete other votes" do
|
||||
@user = create(:user)
|
||||
vote1 = create(:post_vote, post: @post, user: @user, score: 1)
|
||||
vote2 = create(:post_vote, post: @post, user: @user, score: -1)
|
||||
|
||||
assert_equal(-1, @post.reload.score)
|
||||
assert_equal(0, @post.up_score)
|
||||
assert_equal(-1, @post.down_score)
|
||||
assert_equal(0, @post.votes.active.positive.count)
|
||||
assert_equal(1, @post.votes.active.negative.count)
|
||||
assert_equal(true, vote1.reload.is_deleted?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "destroying" do
|
||||
context "soft deleting" do
|
||||
context "an upvote" do
|
||||
should "decrement the post's score" do
|
||||
vote = create(:post_vote, post: @post, score: 1)
|
||||
assert_equal(1, @post.reload.score)
|
||||
assert_equal(1, @post.up_score)
|
||||
assert_equal(0, @post.down_score)
|
||||
assert_equal(1, @post.votes.count)
|
||||
assert_equal(1, @post.votes.active.count)
|
||||
assert_equal(0, @post.votes.deleted.count)
|
||||
|
||||
vote.destroy
|
||||
vote.soft_delete
|
||||
assert_equal(0, @post.reload.score)
|
||||
assert_equal(0, @post.up_score)
|
||||
assert_equal(0, @post.down_score)
|
||||
assert_equal(0, @post.votes.count)
|
||||
assert_equal(0, @post.votes.active.count)
|
||||
assert_equal(1, @post.votes.deleted.count)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -60,15 +87,49 @@ class PostVoteTest < ActiveSupport::TestCase
|
||||
assert_equal(-1, @post.reload.score)
|
||||
assert_equal(0, @post.up_score)
|
||||
assert_equal(-1, @post.down_score)
|
||||
assert_equal(1, @post.votes.count)
|
||||
assert_equal(1, @post.votes.active.count)
|
||||
assert_equal(0, @post.votes.deleted.count)
|
||||
|
||||
vote.destroy
|
||||
vote.soft_delete
|
||||
assert_equal(0, @post.reload.score)
|
||||
assert_equal(0, @post.up_score)
|
||||
assert_equal(0, @post.down_score)
|
||||
assert_equal(0, @post.votes.count)
|
||||
assert_equal(0, @post.votes.active.count)
|
||||
assert_equal(1, @post.votes.deleted.count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "deleting a vote by another user" do
|
||||
should "leave a mod action" do
|
||||
admin = create(:admin_user, name: "admin")
|
||||
vote = create(:post_vote, post: @post, score: 1)
|
||||
|
||||
vote.soft_delete!(updater: admin)
|
||||
assert_match(/admin deleted post vote #\d+ on post #\d+/, ModAction.post_vote_delete.last.description)
|
||||
end
|
||||
end
|
||||
|
||||
context "undeleting a vote by another user" do
|
||||
setup do
|
||||
@admin = create(:admin_user, name: "admin")
|
||||
@vote = create(:post_vote, post: @post, score: 1)
|
||||
|
||||
@vote.soft_delete!(updater: @admin)
|
||||
@vote.update!(is_deleted: false, updater: @admin)
|
||||
end
|
||||
|
||||
should "restore the score" do
|
||||
assert_equal(1, @post.reload.score)
|
||||
assert_equal(1, @post.up_score)
|
||||
assert_equal(0, @post.down_score)
|
||||
assert_equal(1, @post.votes.active.count)
|
||||
assert_equal(0, @post.votes.deleted.count)
|
||||
end
|
||||
|
||||
should "leave a mod action" do
|
||||
assert_match(/admin undeleted post vote #\d+ on post #\d+/, ModAction.post_vote_undelete.last.description)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user