require 'test_helper'

class PostVoteTest < ActiveSupport::TestCase
  context "A PostVote" do
    setup do
      @post = create(:post)
    end

    context "during validation" do
      subject { build(:post_vote, post: @post) }

      should validate_inclusion_of(:score).in_array([-1, 1]).with_message("must be 1 or -1")
    end

    context "creating" do
      context "an upvote" do
        should "increment 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.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

      context "a downvote" do
        should "decrement the post's score" do
          vote = create(:post_vote, post: @post, score: -1)

          assert_equal(-1, @post.reload.score)
          assert_equal(0, @post.up_score)
          assert_equal(-1, @post.down_score)
          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 "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.active.count)
          assert_equal(0, @post.votes.deleted.count)

          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.active.count)
          assert_equal(1, @post.votes.deleted.count)
        end
      end

      context "a downvote" do
        should "increment the post's score" do
          vote = create(:post_vote, post: @post, score: -1)
          assert_equal(-1, @post.reload.score)
          assert_equal(0, @post.up_score)
          assert_equal(-1, @post.down_score)
          assert_equal(1, @post.votes.active.count)
          assert_equal(0, @post.votes.deleted.count)

          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.active.count)
          assert_equal(1, @post.votes.deleted.count)
        end
      end

      context "the same vote more than once" do
        should "not increment the score more than once" do
          vote1 = create(:post_vote, score: 1)
          vote2 = PostVote.find(vote1.id)
          post = vote1.post

          assert_equal(1, post.reload.score)

          vote1.locked_update(is_deleted: true, updater: vote1.user)
          vote2.locked_update(is_deleted: true, updater: vote2.user)

          assert_equal(0, post.reload.score)
        end
      end
    end

    context "deleting a vote by another user" do
      should "leave a mod action" do
        admin = create(:admin_user)
        vote = create(:post_vote, post: @post, score: 1)

        vote.soft_delete!(updater: admin)
        assert_match(/deleted post vote #\d+ on post #\d+/, ModAction.post_vote_delete.last.description)
        assert_equal(vote, ModAction.post_vote_delete.last.subject)
        assert_equal(admin, ModAction.post_vote_delete.last.creator)
      end
    end

    context "undeleting a vote by another user" do
      setup do
        @admin = create(:admin_user)
        @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(/undeleted post vote #\d+ on post #\d+/, ModAction.post_vote_undelete.last.description)
        assert_equal(@vote, ModAction.post_vote_undelete.last.subject)
        assert_equal(@admin, ModAction.post_vote_undelete.last.creator)
      end
    end
  end
end
