require "spec_helper"

describe CommentsController do
  include LoginMacros
  include RedirectExpectationHelper

  let(:comment) { create(:comment) }
  let(:unreviewed_comment) { create(:comment, :unreviewed) }

  before do
    request.env["HTTP_REFERER"] = "/where_i_came_from"
  end

  describe "GET #unreviewed" do
    context "when the commentable is a chapter of a work" do
      let(:user) { create(:user) }
      let(:work) { create(:work, authors: [user.default_pseud], moderated_commenting_enabled: true) }

      it "redirects logged out users to login path with an error" do
        get :unreviewed, params: { work_id: work.id }
        it_redirects_to_with_error(new_user_session_path, "Sorry, you don't have permission to see those unreviewed comments.")
      end

      it "redirects to root path with an error when logged in user does not own the commentable" do
        fake_login
        get :unreviewed, params: { work_id: work.id }
        it_redirects_to_with_error(root_path, "Sorry, you don't have permission to see those unreviewed comments.")
      end

      it "renders the :unreviewed template for a user who owns the work" do
        fake_login_known_user(user)
        get :unreviewed, params: { work_id: work.id }
        expect(response).to render_template("unreviewed")
      end

      it "renders the :unreviewed template for an admin" do
        fake_login_admin(create(:admin))
        get :unreviewed, params: { work_id: work.id }
        expect(response).to render_template("unreviewed")
      end
    end

    context "when the commentable is an admin post" do
      let(:admin_post) { create(:admin_post, moderated_commenting_enabled: true) }

      it "redirects logged out users to login path with an error" do
        get :unreviewed, params: { admin_post_id: admin_post.id }
        it_redirects_to_with_error(new_user_session_path, "Sorry, you don't have permission to see those unreviewed comments.")
      end

      it "redirects logged in users to root path with an error" do
        fake_login
        get :unreviewed, params: { admin_post_id: admin_post.id }
        it_redirects_to_with_error(root_path, "Sorry, you don't have permission to see those unreviewed comments.")
      end

      it "renders the :unreviewed template for an admin" do
        fake_login_admin(create(:admin))
        get :unreviewed, params: { admin_post_id: admin_post.id }
        expect(response).to render_template("unreviewed")
      end
    end
  end
  
  describe "PUT #review_all" do
    context "when commentable is a chapter on a work" do
      let(:work) { unreviewed_comment.commentable.work }
      let(:user) { work.users.first }

      it "redirects logged out user to root path with error and does not mark comment reviewed" do
        put :review_all, params: { work_id: work.id }
        it_redirects_to_with_error(root_path, "What did you want to review comments on?")
        expect(unreviewed_comment.reload.unreviewed).to be_truthy
      end

      context "when logged in" do
        context "when current user does not own the work" do
          it "redirects to root path with error and does not mark comment reviewed" do
            fake_login
            put :review_all, params: { work_id: work.id }
            it_redirects_to_with_error(root_path, "What did you want to review comments on?")
            expect(unreviewed_comment.reload.unreviewed).to be_truthy
          end
        end

        context "when current user owns the work" do
          it "redirects to commentable with notice and marks comment reviewed" do
            fake_login_known_user(user)
            put :review_all, params: { work_id: work.id }
            it_redirects_to_with_notice(work_path(work), "All moderated comments approved.")
            expect(unreviewed_comment.reload.unreviewed).to be_falsey
          end
        end
      end

      it "redirects logged in admin to root path with error and does not mark comment reviewed" do
        fake_login_admin(create(:admin))
        put :review_all, params: { work_id: work.id }
        it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
        expect(unreviewed_comment.reload.unreviewed).to be_truthy
      end
    end

    context "when commentable is an admin post" do
      let(:admin_post) { create(:admin_post, moderated_commenting_enabled: true) }
      let!(:comment1) { create(:comment, :unreviewed, commentable: admin_post) }
      let!(:comment2) { create(:comment, :unreviewed, commentable: admin_post) }

      it "redirects logged out user to root path with error and does not mark comments reviewed" do
        put :review_all, params: { admin_post_id: admin_post.id }
        it_redirects_to_with_error(root_path, "What did you want to review comments on?")
        expect(comment1.reload.unreviewed).to be_truthy
        expect(comment2.reload.unreviewed).to be_truthy
      end

      it "redirects logged in user to root path with error and does not mark comments reviewed" do
        fake_login
        put :review_all, params: { admin_post_id: admin_post.id }
        it_redirects_to_with_error(root_path, "What did you want to review comments on?")
        expect(comment1.reload.unreviewed).to be_truthy
        expect(comment2.reload.unreviewed).to be_truthy
      end

      it "redirects logged in admin to commentable with notice and marks comments reviewed" do
        fake_login_admin(create(:admin))
        put :review_all, params: { admin_post_id: admin_post.id }
        it_redirects_to_with_notice(admin_post_path(admin_post), "All moderated comments approved.")
        expect(comment1.reload.unreviewed).to be_falsey
        expect(comment2.reload.unreviewed).to be_falsey
      end
    end
  end

  describe "PUT #review" do
    context "when commentable is a chapter on a work" do
      let!(:user) { create(:user) }
      let!(:work) { create(:work, authors: [user.default_pseud], moderated_commenting_enabled: true) }
      let(:comment) { create(:comment, :unreviewed, commentable: work.first_chapter) }
      let!(:inbox_comment) { create(:inbox_comment, feedback_comment: comment, user: user, read: false) }

      context "when logged out" do
        it "redirects to 404, does not mark comment reviewed, and does not mark work owner's inbox comment read" do
          put :review, params: { id: comment.id }
          it_redirects_to_simple("/404")
          expect(comment.reload.unreviewed).to be_truthy
          expect(inbox_comment.reload.read).to be_falsey
        end
      end

      context "when logged in" do
        context "when user owns the work" do
          before do
            fake_login_known_user(user)
          end

          context "with approved_from params set to inbox" do
            it "redirects to user inbox path with success message, marks comment reviewed, and marks inbox comment read" do
              put :review, params: { id: comment.id, approved_from: "inbox" }
              it_redirects_to_with_notice(user_inbox_path(user), "Comment approved.")
              expect(comment.reload.unreviewed).to be_falsey
              expect(inbox_comment.reload.read).to be_truthy
            end
          end

          context "with approved_from params set to inbox with filters" do
            it "redirects to filtered user inbox path with success message, marks comment reviewed, and marks inbox comment read" do
              put :review, params: { id: comment.id, approved_from: "inbox", filters: { date: "asc" } }
              expect(response).to redirect_to(user_inbox_path(user, filters: { date: "asc" }))
              expect(flash[:notice]).to eq "Comment approved."
              expect(comment.reload.unreviewed).to be_falsey
              expect(inbox_comment.reload.read).to be_truthy
            end
          end

          context "with approved_from params set to home" do
            it "redirects to root path with success message, marks comment reviewed, and marks inbox comment read" do
              put :review, params: { id: comment.id, approved_from: "home" }
              it_redirects_to_with_notice(root_path, "Comment approved.")
              expect(comment.reload.unreviewed).to be_falsey
              expect(inbox_comment.reload.read).to be_truthy
            end
          end

          context "without approved_from params" do
            it "redirects to unreviewed comments with notice, marks comment reviewed, and marks inbox comment read" do
              put :review, params: { id: comment.id }
              it_redirects_to_with_notice(unreviewed_work_comments_path(work), "Comment approved.")
              expect(comment.reload.unreviewed).to be_falsey
              expect(inbox_comment.reload.read).to be_truthy
            end
          end
        end

        context "when user does not own the work" do
          it "redirects to 404, does not mark comment reviewed, and does not mark work owner's inbox comment read" do
            fake_login
            put :review, params: { id: comment.id }
            it_redirects_to_simple("/404")
            expect(comment.reload.unreviewed).to be_truthy
            expect(inbox_comment.reload.read).to be_falsey
          end
        end
      end

      context "when logged in as admin" do
        it "redirects to rooth path with error, does not mark comment reviewed, and does not mark work owner's inbox comment read" do
          fake_login_admin(create(:admin))
          put :review, params: { id: comment.id }
          it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
          expect(comment.reload.unreviewed).to be_truthy
          expect(inbox_comment.reload.read).to be_falsey
        end
      end
    end

    context "when commentable is an admin post" do
      let(:admin_post) { create(:admin_post, moderated_commenting_enabled: true) }
      let(:comment) { create(:comment, :unreviewed, commentable: admin_post) }

      context "when logged out" do
        it "redirects to 404 and does not mark comment reviewed" do
          put :review, params: { id: comment.id }
          it_redirects_to_simple("/404")
          expect(comment.reload.unreviewed).to be_truthy
        end
      end

      context "when logged in" do
        it "redirects to 404 and does not mark comment reviewed" do
          fake_login
          put :review, params: { id: comment.id }
          it_redirects_to_simple("/404")
          expect(comment.reload.unreviewed).to be_truthy
        end
      end

      context "when logged in as admin" do
        it "redirects to unreviewed comments with notice and marks comment reviewed" do
          fake_login_admin(create(:admin))
          put :review, params: { id: comment.id }
          it_redirects_to_with_notice(unreviewed_admin_post_comments_path(admin_post), "Comment approved.")
          expect(comment.reload.unreviewed).to be_falsey
        end
      end
    end
  end

  describe "PUT #reject" do
    shared_examples "a comment that can only be rejected by an authorized admin" do
      it "doesn't mark the comment as spam and redirects with an error" do
        put :reject, params: { id: comment.id }
        expect(comment.reload.approved).to be_truthy
        it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
      end
    end

    shared_examples "a comment the logged-in user can't reject" do
      it "doesn't mark the comment as spam and redirects with an error" do
        put :reject, params: { id: comment.id }
        expect(comment.reload.approved).to be_truthy
        it_redirects_to_with_error(root_path, "Sorry, you don't have permission to moderate that comment.")
      end
    end

    shared_examples "marking a comment spam" do
      context "when ultimate parent is an AdminPost" do
        let(:admin_post) { create(:admin_post) }
        authorized_roles = %w[superadmin board board_assistants_team communications elections policy_and_abuse support]
        unauthorized_roles = Admin::VALID_ROLES - authorized_roles

        before do
          comment.commentable = admin_post
          comment.parent = admin_post
          comment.save
          comment.reload
        end

        authorized_roles.each do |role|
          context "when logged-in as admin with the role #{role}" do
            before { fake_login_admin(create(:admin, roles: [role])) }

            it "marks the comment as spam" do
              put :reject, params: { id: comment.id }
              expect(flash[:error]).to be_nil
              expect(response).to redirect_to(admin_post_path(comment.ultimate_parent,
                                                              show_comments: true,
                                                              anchor: "comments"))
              expect(comment.reload.approved).to be_falsey
            end
          end
        end

        unauthorized_roles.each do |role|
          context "when logged-in as admin with the role #{role}" do
            before { fake_login_admin(create(:admin, roles: [role])) }

            it_behaves_like "a comment that can only be rejected by an authorized admin"
          end

          context "when logged-in as admin with no role" do
            before { fake_login_admin(create(:admin, roles: [role])) }

            it_behaves_like "a comment that can only be rejected by an authorized admin"
          end
        end
      end

      context "when ultimate parent is a Work" do
        context "when logged-in as admin" do
          authorized_roles = %w[superadmin board policy_and_abuse support]
          unauthorized_roles = Admin::VALID_ROLES - authorized_roles

          authorized_roles.each do |role|
            context "with the role #{role}" do
              before { fake_login_admin(create(:admin, roles: [role])) }

              it "marks the comment as spam" do
                put :reject, params: { id: comment.id }
                expect(flash[:error]).to be_nil
                expect(response).to redirect_to(work_path(comment.ultimate_parent,
                                                          show_comments: true,
                                                          anchor: "comments"))
                expect(comment.reload.approved).to be_falsey
              end
            end
          end

          unauthorized_roles.each do |role|
            context "with the role #{role}" do
              before { fake_login_admin(create(:admin, roles: [role])) }

              it_behaves_like "a comment that can only be rejected by an authorized admin"
            end
          end

          context "with no role" do
            before { fake_login_admin(create(:admin)) }

            it_behaves_like "a comment that can only be rejected by an authorized admin"
          end
        end

        context "when logged-in as the work's creator" do
          before { fake_login_known_user(comment.ultimate_parent.users.first) }

          it "marks the comment as spam" do
            put :reject, params: { id: comment.id }
            expect(flash[:error]).to be_nil
            expect(response).to redirect_to(work_path(comment.ultimate_parent,
                                                      show_comments: true,
                                                      anchor: "comments"))
            expect(comment.reload.approved).to be_falsey
          end
        end
      end

      context "when logged-in as the comment writer" do
        before { fake_login_known_user(comment.pseud.user) }

        it_behaves_like "a comment the logged-in user can't reject"
      end

      context "when logged-in as a random user" do
        before { fake_login }

        it_behaves_like "a comment the logged-in user can't reject"
      end

      context "when not logged-in" do
        before { fake_logout }

        it "doesn't mark the comment as spam and redirects with an error" do
          put :reject, params: { id: comment.id }
          expect(comment.reload.approved).to be_truthy
          it_redirects_to_with_error(
            new_user_session_path,
            "Sorry, you don't have permission to moderate that comment."
          )
        end
      end
    end

    it_behaves_like "marking a comment spam"

    context "when comment is frozen" do
      let(:comment) { create(:comment, iced: true) }

      it_behaves_like "marking a comment spam"
    end
  end

  describe "PUT #approve" do
    before { comment.update_column(:approved, false) }

    shared_examples "a comment that can only be approved by an authorized admin" do
      it "leaves the comment marked as spam and redirects with an error" do
        put :approve, params: { id: comment.id }
        expect(comment.reload.approved).to be_falsey
        it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
      end
    end

    shared_examples "a comment the logged-in user can't approve" do
      it "doesn't mark the comment as spam and redirects with an error" do
        put :approve, params: { id: comment.id }
        expect(comment.reload.approved).to be_falsey
        it_redirects_to_with_error(root_path, "Sorry, you don't have permission to moderate that comment.")
      end
    end

    context "when ultimate parent is an AdminPost" do
      let(:admin) { create(:admin) }
      let(:comment) { create(:comment, :on_admin_post) }
      authorized_roles = %w[superadmin board board_assistants_team communications elections policy_and_abuse support]
      unauthorized_roles = Admin::VALID_ROLES - authorized_roles

      authorized_roles.each do |role|
        context "when logged-in as admin with the role #{role}" do
          it "marks the comment as not spam" do
            fake_login_admin(create(:admin, roles: [role]))
            put :approve, params: { id: comment.id }
            expect(flash[:error]).to be_nil
            expect(response).to redirect_to(admin_post_path(comment.ultimate_parent,
                                                            show_comments: true,
                                                            anchor: "comments"))
            expect(comment.reload.approved).to be_truthy
          end
        end
      end

      unauthorized_roles.each do |role|
        context "when logged-in as admin with the role #{role}" do
          before { fake_login_admin(create(:admin, roles: [role])) }

          it_behaves_like "a comment that can only be approved by an authorized admin"
        end
      end
    end

    context "when ultimate parent is a Work" do
      let(:admin) { create(:admin) }
      authorized_roles = %w[superadmin board policy_and_abuse support]
      unauthorized_roles = Admin::VALID_ROLES - authorized_roles

      authorized_roles.each do |role|
        context "when logged-in as admin with the role #{role}" do
          before { fake_login_admin(create(:admin, roles: [role])) }

          it "marks the comment as not spam" do
            put :approve, params: { id: comment.id }
            expect(flash[:error]).to be_nil
            expect(response).to redirect_to(work_path(comment.ultimate_parent,
                                                      show_comments: true,
                                                      anchor: "comments"))
            expect(comment.reload.approved).to be_truthy
          end
        end
      end

      unauthorized_roles.each do |role|
        context "when logged-in as admin with the role #{role}" do
          before { fake_login_admin(create(:admin, roles: [role])) }

          it_behaves_like "a comment that can only be approved by an authorized admin"
        end

        context "when logged-in as admin with no role" do
          before { fake_login_admin(create(:admin)) }

          it_behaves_like "a comment that can only be approved by an authorized admin"
        end
      end

      context "when logged-in as the work's creator" do
        before { fake_login_known_user(comment.ultimate_parent.users.first) }

        it_behaves_like "a comment that can only be approved by an authorized admin"
      end
    end

    context "when logged-in as the comment writer" do
      before { fake_login_known_user(comment.pseud.user) }

      it_behaves_like "a comment the logged-in user can't approve"
    end

    context "when logged-in as a random user" do
      before { fake_login }

      it_behaves_like "a comment the logged-in user can't approve"
    end

    context "when not logged-in" do
      before { fake_logout }

      it "leaves the comment marked as spam and redirects with an error" do
        put :approve, params: { id: comment.id }
        expect(comment.reload.approved).to be_falsey
        it_redirects_to_with_error(
          new_user_session_path,
          "Sorry, you don't have permission to moderate that comment."
        )
      end
    end
  end
end
  