require 'rails_helper'

describe InvitesController do

  context '.show' do
    render_views

    it "shows error if invite not found" do
      get :show, params: { id: 'nopeNOPEnope' }

      expect(response).to be_success

      body = response.body

      expect(body).to_not have_tag(:script, with: { src: '/assets/application.js' })
      expect(CGI.unescapeHTML(body)).to include(I18n.t('invite.not_found'))
    end

    it "renders the accept invite page if invite exists" do
      i = Fabricate(:invite)
      get :show, params: { id: i.invite_key }

      expect(response).to be_success

      body = response.body

      expect(body).to have_tag(:script, with: { src: '/assets/application.js' })
      expect(CGI.unescapeHTML(body)).to_not include(I18n.t('invite.not_found'))
    end
  end

  context '.destroy' do

    it 'requires you to be logged in' do
      delete :destroy,
        params: { email: 'jake@adventuretime.ooo' },
        format: :json
      expect(response.status).to eq(403)
    end

    context 'while logged in' do
      let!(:user) { log_in }
      let!(:invite) { Fabricate(:invite, invited_by: user) }
      let(:another_invite) { Fabricate(:invite, email: 'anotheremail@address.com') }

      it 'raises an error when the email is missing' do
        expect { delete :destroy, format: :json }.to raise_error(ActionController::ParameterMissing)
      end

      it "raises an error when the email cannot be found" do
        delete :destroy, params: { email: 'finn@adventuretime.ooo' }, format: :json
        expect(response.status).to eq(400)
      end

      it 'raises an error when the invite is not yours' do
        delete :destroy, params: { email: another_invite.email }, format: :json
        expect(response.status).to eq(400)
      end

      it "destroys the invite" do
        Invite.any_instance.expects(:trash!).with(user)
        delete :destroy, params: { email: invite.email }, format: :json
      end

    end

  end

  context '#create' do
    it 'requires you to be logged in' do
      post :create, params: { email: 'jake@adventuretime.ooo' }, format: :json
      expect(response.status).to eq(403)
    end

    context 'while logged in' do
      let(:email) { 'jake@adventuretime.ooo' }

      it "fails if you can't invite to the forum" do
        log_in
        post :create, params: { email: email }, format: :json
        expect(response).not_to be_success
      end

      it "fails for normal user if invite email already exists" do
        user = log_in(:trust_level_4)
        invite = Invite.invite_by_email("invite@example.com", user)
        invite.reload
        post :create, params: { email: invite.email }, format: :json
        expect(response).not_to be_success
        json = JSON.parse(response.body)
        expect(json["failed"]).to be_present
      end

      it "allows admins to invite to groups" do
        group = Fabricate(:group)
        log_in(:admin)
        post :create, params: { email: email, group_names: group.name }, format: :json
        expect(response).to be_success
        expect(Invite.find_by(email: email).invited_groups.count).to eq(1)
      end

      it 'allows group owners to invite to groups' do
        group = Fabricate(:group)
        user = log_in
        user.update!(trust_level: TrustLevel[2])
        group.add_owner(user)

        post :create, params: { email: email, group_names: group.name }, format: :json

        expect(response).to be_success
        expect(Invite.find_by(email: email).invited_groups.count).to eq(1)
      end

      it "allows admin to send multiple invites to same email" do
        user = log_in(:admin)
        invite = Invite.invite_by_email("invite@example.com", user)
        invite.reload
        post :create, params: { email: invite.email }, format: :json
        expect(response).to be_success
      end

      it "responds with error message in case of validation failure" do
        log_in(:admin)
        post :create, params: { email: "test@mailinator.com" }, format: :json
        expect(response).not_to be_success
        json = JSON.parse(response.body)
        expect(json["errors"]).to be_present
      end
    end

  end

  context '.create_invite_link' do
    it 'requires you to be logged in' do
      post :create_invite_link, params: {
        email: 'jake@adventuretime.ooo'
      }, format: :json
      expect(response.status).to eq(403)
    end

    context 'while logged in' do
      let(:email) { 'jake@adventuretime.ooo' }

      it "fails if you can't invite to the forum" do
        log_in
        post :create_invite_link, params: { email: email }, format: :json
        expect(response).not_to be_success
      end

      it "fails for normal user if invite email already exists" do
        user = log_in(:trust_level_4)
        invite = Invite.invite_by_email("invite@example.com", user)
        invite.reload

        post :create_invite_link, params: {
          email: invite.email
        }, format: :json

        expect(response).not_to be_success
      end

      it "verifies that inviter is authorized to invite new user to a group-private topic" do
        group = Fabricate(:group)
        private_category = Fabricate(:private_category, group: group)
        group_private_topic = Fabricate(:topic, category: private_category)
        log_in(:trust_level_4)

        post :create_invite_link, params: {
          email: email, topic_id: group_private_topic.id
        }, format: :json

        expect(response).not_to be_success
      end

      it "allows admins to invite to groups" do
        group = Fabricate(:group)
        log_in(:admin)

        post :create_invite_link, params: {
          email: email, group_names: group.name
        }, format: :json

        expect(response).to be_success
        expect(Invite.find_by(email: email).invited_groups.count).to eq(1)
      end

      it "allows multiple group invite" do
        Fabricate(:group, name: "security")
        Fabricate(:group, name: "support")
        log_in(:admin)

        post :create_invite_link, params: {
          email: email, group_names: "security,support"
        }, format: :json

        expect(response).to be_success
        expect(Invite.find_by(email: email).invited_groups.count).to eq(2)
      end
    end
  end

  context '.perform_accept_invitation' do

    context 'with an invalid invite id' do
      before do
        put :perform_accept_invitation, params: { id: "doesn't exist" }, format: :json
      end

      it "redirects to the root" do
        expect(response).to be_success
        json = JSON.parse(response.body)
        expect(json["success"]).to eq(false)
        expect(json["message"]).to eq(I18n.t('invite.not_found'))
      end

      it "should not change the session" do
        expect(session[:current_user_id]).to be_blank
      end
    end

    context 'with a deleted invite' do
      let(:topic) { Fabricate(:topic) }
      let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
      let(:deleted_invite) { invite.destroy; invite }
      before do
        put :perform_accept_invitation, params: { id: deleted_invite.invite_key }, format: :json
      end

      it "redirects to the root" do
        expect(response).to be_success
        json = JSON.parse(response.body)
        expect(json["success"]).to eq(false)
        expect(json["message"]).to eq(I18n.t('invite.not_found'))
      end

      it "should not change the session" do
        expect(session[:current_user_id]).to be_blank
      end
    end

    context 'with a valid invite id' do
      let(:topic) { Fabricate(:topic) }
      let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }

      it 'redeems the invite' do
        Invite.any_instance.expects(:redeem)
        put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json
      end

      context 'when redeem returns a user' do
        let(:user) { Fabricate(:coding_horror) }

        context 'success' do
          subject { put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json }

          before do
            Invite.any_instance.expects(:redeem).returns(user)
          end

          it 'logs in the user' do
            events = DiscourseEvent.track_events { subject }

            expect(events.map { |event| event[:event_name] }).to include(
              :user_logged_in, :user_first_logged_in
            )

            expect(session[:current_user_id]).to eq(user.id)
          end

          it 'redirects to the first topic the user was invited to' do
            subject
            json = JSON.parse(response.body)
            expect(json["success"]).to eq(true)
            expect(json["redirect_to"]).to eq(topic.relative_url)
          end
        end

        context 'failure' do
          subject { put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json }

          it "doesn't log in the user if there's a validation error" do
            user.errors.add(:password, :common)
            Invite.any_instance.expects(:redeem).raises(ActiveRecord::RecordInvalid.new(user))
            subject
            expect(response).to be_success
            json = JSON.parse(response.body)
            expect(json["success"]).to eq(false)
            expect(json["errors"]["password"]).to be_present
          end
        end

        context '.post_process_invite' do
          before do
            Invite.any_instance.stubs(:redeem).returns(user)
            Jobs.expects(:enqueue).with(:invite_email, has_key(:invite_id))
            user.password_hash = nil
          end

          it 'sends a welcome message if set' do
            user.send_welcome_message = true
            user.expects(:enqueue_welcome_message).with('welcome_invite')
            Jobs.expects(:enqueue).with(:invite_password_instructions_email, has_entries(username: user.username))
            put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json
          end

          it "sends password reset email if password is not set" do
            user.expects(:enqueue_welcome_message).with('welcome_invite').never
            Jobs.expects(:enqueue).with(:invite_password_instructions_email, has_entries(username: user.username))
            put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json
          end

          it "does not send password reset email if sso is enabled" do
            SiteSetting.sso_url = "https://www.example.com/sso"
            SiteSetting.enable_sso = true
            Jobs.expects(:enqueue).with(:invite_password_instructions_email, has_key(:username)).never
            put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json
          end

          it "does not send password reset email if local login is disabled" do
            SiteSetting.enable_local_logins = false
            Jobs.expects(:enqueue).with(:invite_password_instructions_email, has_key(:username)).never
            put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json
          end

          it 'sends an activation email if password is set' do
            user.password_hash = 'qaw3ni3h2wyr63lakw7pea1nrtr44pls'
            Jobs.expects(:enqueue).with(:invite_password_instructions_email, has_key(:username)).never
            Jobs.expects(:enqueue).with(:critical_user_email, has_entries(type: :signup, user_id: user.id))
            put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json
          end
        end
      end
    end

    context 'new registrations are disabled' do
      let(:topic) { Fabricate(:topic) }
      let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
      before { SiteSetting.allow_new_registrations = false }

      it "doesn't redeem the invite" do
        Invite.any_instance.stubs(:redeem).never
        put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json
      end
    end

    context 'user is already logged in' do
      let!(:user) { log_in }
      let(:topic) { Fabricate(:topic) }
      let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }

      it "doesn't redeem the invite" do
        Invite.any_instance.stubs(:redeem).never
        put :perform_accept_invitation, params: { id: invite.invite_key }, format: :json
      end
    end
  end

  context '.resend_invite' do

    it 'requires you to be logged in' do
      delete :resend_invite, params: { email: 'first_name@example.com' }, format: :json
      expect(response.status).to eq(403)
    end

    context 'while logged in' do
      let!(:user) { log_in }
      let!(:invite) { Fabricate(:invite, invited_by: user) }
      let(:another_invite) { Fabricate(:invite, email: 'last_name@example.com') }

      it 'raises an error when the email is missing' do
        expect { post :resend_invite, format: :json }.to raise_error(ActionController::ParameterMissing)
      end

      it "raises an error when the email cannot be found" do
        post :resend_invite, params: { email: 'first_name@example.com' }, format: :json
        expect(response.status).to eq(400)
      end

      it 'raises an error when the invite is not yours' do
        post :resend_invite, params: { email: another_invite.email }, format: :json
        expect(response.status).to eq(400)
      end

      it "resends the invite" do
        Invite.any_instance.expects(:resend_invite)
        post :resend_invite, params: { email: invite.email }, format: :json
      end

    end

  end

  context '.upload_csv' do
    it 'requires you to be logged in' do
      post :upload_csv, format: :json
      expect(response.status).to eq(403)
    end

    context 'while logged in' do
      let(:csv_file) { File.new("#{Rails.root}/spec/fixtures/csv/discourse.csv") }

      let(:file) do
        Rack::Test::UploadedFile.new(File.open(csv_file))
      end

      let(:filename) { 'discourse.csv' }

      it "fails if you can't bulk invite to the forum" do
        log_in
        post :upload_csv, params: { file: file, name: filename }, format: :json
        expect(response).not_to be_success
      end

      it "allows admin to bulk invite" do
        log_in(:admin)
        post :upload_csv, params: { file: file, name: filename }, format: :json
        expect(response).to be_success
      end
    end

  end

end
