spec/integration/api/messages_controller_spec.rb
# frozen_string_literal: true

require_relative "api_spec_helper"

describe Api::V1::MessagesController do
  let(:auth) {
    FactoryBot.create(
      :auth_with_default_scopes,
      scopes: %w[openid conversations],
      user:   FactoryBot.create(:user, profile: FactoryBot.create(:profile_with_image_url))
    )
  }

  let(:auth_minimum_scopes) {
    FactoryBot.create(:auth_with_default_scopes)
  }

  let!(:access_token) { auth.create_access_token.to_s }
  let!(:access_token_minimum_scopes) { auth_minimum_scopes.create_access_token.to_s }
  let(:invalid_token) { SecureRandom.hex(9) }

  before do
    auth.user.seed_aspects
    auth.user.share_with(bob.person, auth.user.aspects[0])
    auth.user.share_with(alice.person, auth.user.aspects[0])
    auth.user.share_with(auth_minimum_scopes.user.person, auth.user.aspects[0])
    alice.share_with auth.user.person, alice.aspects[0]
    auth_minimum_scopes.user.seed_aspects
    auth_minimum_scopes.user.share_with(auth.user.person, auth_minimum_scopes.user.aspects[0])

    @conversation = {
      subject:      "new conversation",
      body:         "first message",
      recipients:   [alice.guid],
      access_token: access_token
    }

    @message_text = "reply to first message"
  end

  describe "#create" do
    before do
      post api_v1_conversations_path, params: @conversation
      @conversation_guid = JSON.parse(response.body)["guid"]
    end

    context "with valid data" do
      it "creates the message in the conversation scope" do
        post(
          api_v1_conversation_messages_path(@conversation_guid),
          params: {body: @message_text, access_token: access_token}
        )
        expect(response.status).to eq(201)

        message = JSON.parse(response.body)
        confirm_message_format(message, @message_text, auth.user)

        get(
          api_v1_conversation_messages_path(@conversation_guid),
          params: {access_token: access_token}
        )
        messages = response_body_data(response)
        expect(messages.length).to eq(2)
        confirm_message_format(messages[1], @message_text, auth.user)
      end
    end

    context "without valid data" do
      it "no data returns a unprocessable entity (422)" do
        post(
          api_v1_conversation_messages_path(@conversation_guid),
          params: {access_token: access_token}
        )
        confirm_api_error(response, 422, "Couldn’t accept or process the conversation")
      end

      it "empty string returns a unprocessable entity (422)" do
        post(
          api_v1_conversation_messages_path(@conversation_guid),
          params: {body: "", access_token: access_token}
        )
        confirm_api_error(response, 422, "Couldn’t accept or process the conversation")
      end
    end

    context "with wrong conversation id" do
      it "returns a a not found error (404)" do
        post(
          api_v1_conversation_messages_path(42),
          params: {access_token: access_token}
        )
        confirm_api_error(response, 404, "Conversation with provided guid could not be found")
      end
    end

    context "improper credentials" do
      it "fails without conversation token" do
        post(
          api_v1_conversation_messages_path(@conversation_guid),
          params: {body: @message_text, access_token: access_token_minimum_scopes}
        )
        expect(response.status).to eq(403)
      end

      it "fails when not logged in" do
        post(
          api_v1_conversation_messages_path(@conversation_guid),
          params: {body: @message_text, access_token: invalid_token}
        )
        expect(response.status).to eq(401)
      end
    end
  end

  describe "#index" do
    before do
      post api_v1_conversations_path, params: @conversation
      @conversation_guid = JSON.parse(response.body)["guid"]
      Conversation.find_by(guid: @conversation_guid)
                  .conversation_visibilities
                  .where(person: auth.user.person)
                  .update(unread: true)
    end

    context "retrieving messages" do
      it "returns all messages related to conversation" do
        get(
          api_v1_conversation_messages_path(@conversation_guid),
          params: {access_token: access_token}
        )
        expect(response.status).to eq(200)
        messages = response_body_data(response)
        expect(messages.length).to eq(1)

        expect_to_match_json_schema(messages.to_json, "#/definitions/messages")

        confirm_message_format(messages[0], "first message", auth.user)
        conversation = get_conversation(@conversation_guid)
        expect(conversation[:read]).to be_falsy
      end

      context "improper credentials" do
        it "fails without conversation token" do
          get(
            api_v1_conversation_messages_path(@conversation_guid),
            params: {access_token: access_token_minimum_scopes}
          )
          expect(response.status).to eq(403)
        end

        it "fails when not logged in" do
          get(
            api_v1_conversation_messages_path(@conversation_guid),
            params: {access_token: invalid_token}
          )
          expect(response.status).to eq(401)
        end
      end
    end
  end

  private

  def response_body_data(response)
    JSON.parse(response.body)
  end

  def get_conversation(conversation_id)
    conversation_service = ConversationService.new(auth.user)
    raw_conversation = conversation_service.find!(conversation_id)
    ConversationPresenter.new(raw_conversation, auth.user).as_api_json
  end

  def confirm_message_format(message, ref_message, author)
    expect(message["guid"]).to_not be_nil
    expect(message["created_at"]).to_not be_nil
    expect(message["body"]).to eq ref_message
    confirm_person_format(message["author"], author)
  end

  # rubocop:disable Metrics/AbcSize
  def confirm_person_format(post_person, user)
    expect(post_person["guid"]).to eq(user.guid)
    expect(post_person["diaspora_id"]).to eq(user.diaspora_handle)
    expect(post_person["name"]).to eq(user.name)
    expect(post_person["avatar"]).to eq(user.profile.image_url(size: :thumb_medium))
  end
  # rubocop:enable Metrics/AbcSize
end
