Instruction:
Write RSpec test file for following ruby class
```ruby
# Find the various telegram payload samples here: https://core.telegram.org/bots/webhooks#testing-your-bot-with-updates
# https://core.telegram.org/bots/api#available-types

class Telegram::IncomingMessageService
  include ::FileTypeHelper
  include ::Telegram::ParamHelpers
  pattr_initialize [:inbox!, :params!]

  def perform
    # chatwoot doesn't support group conversations at the moment
    return unless private_message?

    set_contact
    update_contact_avatar
    set_conversation
    @message = @conversation.messages.build(
      content: telegram_params_message_content,
      account_id: @inbox.account_id,
      inbox_id: @inbox.id,
      message_type: :incoming,
      sender: @contact,
      content_attributes: telegram_params_content_attributes,
      source_id: telegram_params_message_id.to_s
    )

    process_message_attachments if message_params?
    @message.save!
  end

  private

  def set_contact
    contact_inbox = ::ContactInboxWithContactBuilder.new(
      source_id: telegram_params_from_id,
      inbox: inbox,
      contact_attributes: contact_attributes
    ).perform

    @contact_inbox = contact_inbox
    @contact = contact_inbox.contact
  end

  def process_message_attachments
    attach_location
    attach_files
  end

  def update_contact_avatar
    return if @contact.avatar.attached?

    avatar_url = inbox.channel.get_telegram_profile_image(telegram_params_from_id)
    ::Avatar::AvatarFromUrlJob.perform_later(@contact, avatar_url) if avatar_url
  end

  def conversation_params
    {
      account_id: @inbox.account_id,
      inbox_id: @inbox.id,
      contact_id: @contact.id,
      contact_inbox_id: @contact_inbox.id,
      additional_attributes: conversation_additional_attributes
    }
  end

  def set_conversation
    @conversation = @contact_inbox.conversations.first
    return if @conversation

    @conversation = ::Conversation.create!(conversation_params)
  end

  def contact_attributes
    {
      name: "#{telegram_params_first_name} #{telegram_params_last_name}",
      additional_attributes: additional_attributes
    }
  end

  def additional_attributes
    {
      username: telegram_params_username,
      language_code: telegram_params_language_code
    }
  end

  def conversation_additional_attributes
    {
      chat_id: telegram_params_chat_id
    }
  end

  def file_content_type
    return :image if params[:message][:photo].present? || params.dig(:message, :sticker, :thumb).present?
    return :audio if params[:message][:voice].present? || params[:message][:audio].present?
    return :video if params[:message][:video].present?

    file_type(params[:message][:document][:mime_type])
  end

  def attach_files
    return unless file

    attachment_file = Down.download(
      inbox.channel.get_telegram_file_path(file[:file_id])
    )

    @message.attachments.new(
      account_id: @message.account_id,
      file_type: file_content_type,
      file: {
        io: attachment_file,
        filename: attachment_file.original_filename,
        content_type: attachment_file.content_type
      }
    )
  end

  def attach_location
    return unless location

    @message.attachments.new(
      account_id: @message.account_id,
      file_type: :location,
      coordinates_lat: location['latitude'],
      coordinates_long: location['longitude']
    )
  end

  def file
    @file ||= visual_media_params || params[:message][:voice].presence || params[:message][:audio].presence || params[:message][:document].presence
  end

  def location
    @location ||= params.dig(:message, :location).presence
  end

  def visual_media_params
    params[:message][:photo].presence&.last || params.dig(:message, :sticker, :thumb).presence || params[:message][:video].presence
  end
end

```

Response:
require 'rails_helper'

describe Telegram::IncomingMessageService do
  before do
    stub_request(:any, /api.telegram.org/).to_return(headers: { content_type: 'application/json' }, body: {}.to_json, status: 200)
    stub_request(:get, 'https://chatwoot-assets.local/sample.png').to_return(
      status: 200,
      body: File.read('spec/assets/sample.png'),
      headers: {}
    )
    stub_request(:get, 'https://chatwoot-assets.local/sample.mov').to_return(
      status: 200,
      body: File.read('spec/assets/sample.mov'),
      headers: {}
    )
    stub_request(:get, 'https://chatwoot-assets.local/sample.mp3').to_return(
      status: 200,
      body: File.read('spec/assets/sample.mp3'),
      headers: {}
    )
    stub_request(:get, 'https://chatwoot-assets.local/sample.ogg').to_return(
      status: 200,
      body: File.read('spec/assets/sample.ogg'),
      headers: {}
    )
    stub_request(:get, 'https://chatwoot-assets.local/sample.pdf').to_return(
      status: 200,
      body: File.read('spec/assets/sample.pdf'),
      headers: {}
    )
  end

  let!(:telegram_channel) { create(:channel_telegram) }
  let!(:message_params) do
    {
      'message_id' => 1,
      'from' => {
        'id' => 23, 'is_bot' => false, 'first_name' => 'Sojan', 'last_name' => 'Jose', 'username' => 'sojan', 'language_code' => 'en'
      },
      'chat' => { 'id' => 23, 'first_name' => 'Sojan', 'last_name' => 'Jose', 'username' => 'sojan', 'type' => 'private' },
      'date' => 1_631_132_077
    }
  end

  describe '#perform' do
    context 'when valid text message params' do
      it 'creates appropriate conversations, message and contacts' do
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => { 'text' => 'test' }.merge(message_params)
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.content).to eq('test')
      end
    end

    context 'when valid caption params' do
      it 'creates appropriate conversations, message and contacts' do
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => { 'caption' => 'test' }.merge(message_params)
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.content).to eq('test')
      end
    end

    context 'when group messages' do
      it 'doesnot create conversations, message and contacts' do
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => {
            'message_id' => 1,
            'from' => {
              'id' => 23, 'is_bot' => false, 'first_name' => 'Sojan', 'last_name' => 'Jose', 'username' => 'sojan', 'language_code' => 'en'
            },
            'chat' => { 'id' => 23, 'first_name' => 'Sojan', 'last_name' => 'Jose', 'username' => 'sojan', 'type' => 'group' },
            'date' => 1_631_132_077, 'text' => 'test'
          }
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).to eq(0)
      end
    end

    context 'when valid audio messages params' do
      it 'creates appropriate conversations, message and contacts' do
        allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.mp3')
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => {
            'audio' => {
              'file_id' => 'AwADBAADbXXXXXXXXXXXGBdhD2l6_XX',
              'duration' => 243,
              'mime_type' => 'audio/mpeg',
              'file_size' => 3_897_500,
              'title' => 'Test music file'
            }
          }.merge(message_params)
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('audio')
      end
    end

    context 'when valid image attachment params' do
      it 'creates appropriate conversations, message and contacts' do
        allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.png')
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => {
            'photo' => [{
              'file_id' => 'AgACAgUAAxkBAAODYV3aGZlD6vhzKsE2WNmblsr6zKwAAi-tMRvCoeBWNQ1ENVBzJdwBAAMCAANzAAMhBA',
              'file_unique_id' => 'AQADL60xG8Kh4FZ4', 'file_size' => 1883, 'width' => 90, 'height' => 67
            }]
          }.merge(message_params)
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('image')
      end
    end

    context 'when valid sticker attachment params' do
      it 'creates appropriate conversations, message and contacts' do
        allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.png')
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => {
            'sticker' => {
              'emoji' => '👍', 'width' => 512, 'height' => 512, 'set_name' => 'a834556273_by_HopSins_1_anim', 'is_animated' => 1,
              'thumb' => {
                'file_id' => 'AAMCAQADGQEAA0dhXpKorj9CiRpNX3QOn7YPZ6XS4AAC4wADcVG-MexptyOf8SbfAQAHbQADIQQ',
                'file_unique_id' => 'AQAD4wADcVG-MXI', 'file_size' => 4690, 'width' => 128, 'height' => 128
              },
              'file_id' => 'CAACAgEAAxkBAANHYV6SqK4_QokaTV90Dp-2D2el0uAAAuMAA3FRvjHsabcjn_Em3yEE',
              'file_unique_id' => 'AgAD4wADcVG-MQ',
              'file_size' => 7340
            }
          }.merge(message_params)
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('image')
      end
    end

    context 'when valid video messages params' do
      it 'creates appropriate conversations, message and contacts' do
        allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.mov')
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => {
            'video' => {
              'duration' => 1, 'width' => 720, 'height' => 1280, 'file_name' => 'IMG_2170.MOV', 'mime_type' => 'video/mp4', 'thumb' => {
                'file_id' => 'AAMCBQADGQEAA4ZhXd78Xz6_c6gCzbdIkgGiXJcwwwACqwMAAp3x8Fbhf3EWamgCWAEAB20AAyEE', 'file_unique_id' => 'AQADqwMAAp3x8FZy',
                'file_size' => 11_462, 'width' => 180, 'height' => 320
              }, 'file_id' => 'BAACAgUAAxkBAAOGYV3e_F8-v3OoAs23SJIBolyXMMMAAqsDAAKd8fBW4X9xFmpoAlghBA', 'file_unique_id' => 'AgADqwMAAp3x8FY',
              'file_size' => 291_286
            }
          }.merge(message_params)
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('video')
      end
    end

    context 'when valid voice attachment params' do
      it 'creates appropriate conversations, message and contacts' do
        allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.ogg')
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => {
            'voice' => {
              'duration' => 2, 'mime_type' => 'audio/ogg', 'file_id' => 'AwACAgUAAxkBAANjYVwnWF_w8LYTchqVdK9dY7mbwYEAAskDAALCoeBWFvS2u4zS6HAhBA',
              'file_unique_id' => 'AgADyQMAAsKh4FY', 'file_size' => 11_833
            }
          }.merge(message_params)
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('audio')
      end
    end

    context 'when valid document message params' do
      it 'creates appropriate conversations, message and contacts' do
        allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.pdf')
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => {
            'document' => {
              'file_id' => 'AwADBAADbXXXXXXXXXXXGBdhD2l6_XX',
              'file_name' => 'Screenshot 2021-09-27 at 2.01.14 PM.png',
              'mime_type' => 'application/png',
              'file_size' => 536_392
            }
          }.merge(message_params)
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('file')
      end
    end

    context 'when valid location message params' do
      it 'creates appropriate conversations, message and contacts' do
        params = {
          'update_id' => 2_342_342_343_242,
          'message' => {
            'location': {
              'latitude': 37.7893768,
              'longitude': -122.3895553
            }
          }.merge(message_params)
        }.with_indifferent_access
        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('location')
      end
    end

    context 'when valid callback_query params' do
      it 'creates appropriate conversations, message and contacts' do
        params = {
          'update_id' => 2_342_342_343_242,
          'callback_query' => {
            'id' => '2342342309929423',
            'from' => {
              'id' => 5_171_248,
              'is_bot' => false,
              'first_name' => 'Sojan',
              'last_name' => 'Jose',
              'username' => 'sojan',
              'language_code' => 'en',
              'is_premium' => true
            },
            'message' => message_params,
            'chat_instance' => '-89923842384923492',
            'data' => 'Option 1'
          }
        }.with_indifferent_access

        described_class.new(inbox: telegram_channel.inbox, params: params).perform
        expect(telegram_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(telegram_channel.inbox.messages.first.content).to eq('Option 1')
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Telegram::SendOnTelegramService < Base::SendOnChannelService
  private

  def channel_class
    Channel::Telegram
  end

  def perform_reply
    ## send reply to telegram message api
    # https://core.telegram.org/bots/api#sendmessage
    message_id = channel.send_message_on_telegram(message)
    message.update!(source_id: message_id) if message_id.present?
  end

  def inbox
    @inbox ||= message.inbox
  end

  def channel
    @channel ||= inbox.channel
  end
end

```

Response:
require 'rails_helper'

describe Telegram::SendOnTelegramService do
  describe '#perform' do
    context 'when a valid message' do
      it 'calls channel.send_message_on_telegram' do
        telegram_request = double
        telegram_channel = create(:channel_telegram)
        message = create(:message, message_type: :outgoing, content: 'test',
                                   conversation: create(:conversation, inbox: telegram_channel.inbox, additional_attributes: { 'chat_id' => '123' }))
        allow(HTTParty).to receive(:post).and_return(telegram_request)
        allow(telegram_request).to receive(:success?).and_return(true)
        allow(telegram_request).to receive(:parsed_response).and_return({ 'result' => { 'message_id' => 'telegram_123' } })
        described_class.new(message: message).perform
        expect(message.source_id).to eq('telegram_123')
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class MessageTemplates::HookExecutionService
  pattr_initialize [:message!]

  def perform
    return if conversation.campaign.present?
    return if conversation.last_incoming_message.blank?

    trigger_templates
  end

  private

  delegate :inbox, :conversation, to: :message
  delegate :contact, to: :conversation

  def trigger_templates
    ::MessageTemplates::Template::OutOfOffice.new(conversation: conversation).perform if should_send_out_of_office_message?
    ::MessageTemplates::Template::Greeting.new(conversation: conversation).perform if should_send_greeting?
    ::MessageTemplates::Template::EmailCollect.new(conversation: conversation).perform if inbox.enable_email_collect && should_send_email_collect?
    ::MessageTemplates::Template::CsatSurvey.new(conversation: conversation).perform if should_send_csat_survey?
  end

  def should_send_out_of_office_message?
    # should not send if its a tweet message
    return false if conversation.tweet?
    # should not send for outbound messages
    return false unless message.incoming?

    inbox.out_of_office? && conversation.messages.today.template.empty? && inbox.out_of_office_message.present?
  end

  def first_message_from_contact?
    conversation.messages.outgoing.count.zero? && conversation.messages.template.count.zero?
  end

  def should_send_greeting?
    # should not send if its a tweet message
    return false if conversation.tweet?

    first_message_from_contact? && inbox.greeting_enabled? && inbox.greeting_message.present?
  end

  def email_collect_was_sent?
    conversation.messages.where(content_type: 'input_email').present?
  end

  # TODO: we should be able to reduce this logic once we have a toggle for email collect messages
  def should_send_email_collect?
    !contact_has_email? && inbox.web_widget? && !email_collect_was_sent?
  end

  def contact_has_email?
    contact.email
  end

  def csat_enabled_conversation?
    return false unless conversation.resolved?
    # should not sent since the link will be public
    return false if conversation.tweet?
    return false unless inbox.csat_survey_enabled?

    true
  end

  def should_send_csat_survey?
    return unless csat_enabled_conversation?

    # only send CSAT once in a conversation
    return if conversation.messages.where(content_type: :input_csat).present?

    true
  end
end
MessageTemplates::HookExecutionService.prepend_mod_with('MessageTemplates::HookExecutionService')

```

Response:
require 'rails_helper'

describe MessageTemplates::HookExecutionService do
  context 'when there is no incoming message in conversation' do
    it 'will not call any hooks' do
      contact = create(:contact, email: nil)
      conversation = create(:conversation, contact: contact)
      # ensure greeting hook is enabled
      conversation.inbox.update(greeting_enabled: true, enable_email_collect: true)

      email_collect_service = double

      allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(email_collect_service)
      allow(email_collect_service).to receive(:perform).and_return(true)
      allow(MessageTemplates::Template::Greeting).to receive(:new)

      # described class gets called in message after commit
      create(:message, conversation: conversation, message_type: 'activity', content: 'Conversation marked resolved!!')

      expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
      expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new)
    end
  end

  context 'when Greeting Message' do
    it 'doesnot calls ::MessageTemplates::Template::Greeting if greeting_message is empty' do
      contact = create(:contact, email: nil)
      conversation = create(:conversation, contact: contact)
      # ensure greeting hook is enabled
      conversation.inbox.update(greeting_enabled: true, enable_email_collect: true)

      email_collect_service = double

      allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(email_collect_service)
      allow(email_collect_service).to receive(:perform).and_return(true)
      allow(MessageTemplates::Template::Greeting).to receive(:new)

      # described class gets called in message after commit
      message = create(:message, conversation: conversation)

      expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
      expect(MessageTemplates::Template::EmailCollect).to have_received(:new).with(conversation: message.conversation)
      expect(email_collect_service).to have_received(:perform)
    end

    it 'will not call ::MessageTemplates::Template::Greeting if its a tweet conversation' do
      twitter_channel = create(:channel_twitter_profile)
      twitter_inbox = create(:inbox, channel: twitter_channel)
      # ensure greeting hook is enabled and greeting_message is present
      twitter_inbox.update(greeting_enabled: true, greeting_message: 'Hi, this is a greeting message')

      conversation = create(:conversation, inbox: twitter_inbox, additional_attributes: { type: 'tweet' })
      greeting_service = double
      allow(MessageTemplates::Template::Greeting).to receive(:new).and_return(greeting_service)
      allow(greeting_service).to receive(:perform).and_return(true)

      message = create(:message, conversation: conversation)
      expect(MessageTemplates::Template::Greeting).not_to have_received(:new).with(conversation: message.conversation)
    end
  end

  context 'when it is a first message from web widget' do
    it 'calls ::MessageTemplates::Template::EmailCollect' do
      contact = create(:contact, email: nil)
      conversation = create(:conversation, contact: contact)

      # ensure greeting hook is enabled and greeting_message is present
      conversation.inbox.update(greeting_enabled: true, enable_email_collect: true, greeting_message: 'Hi, this is a greeting message')

      email_collect_service = double
      greeting_service = double
      allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(email_collect_service)
      allow(email_collect_service).to receive(:perform).and_return(true)
      allow(MessageTemplates::Template::Greeting).to receive(:new).and_return(greeting_service)
      allow(greeting_service).to receive(:perform).and_return(true)

      # described class gets called in message after commit
      message = create(:message, conversation: conversation)

      expect(MessageTemplates::Template::Greeting).to have_received(:new).with(conversation: message.conversation)
      expect(greeting_service).to have_received(:perform)
      expect(MessageTemplates::Template::EmailCollect).to have_received(:new).with(conversation: message.conversation)
      expect(email_collect_service).to have_received(:perform)
    end

    it 'doesnot calls ::MessageTemplates::Template::EmailCollect on campaign conversations' do
      contact = create(:contact, email: nil)
      conversation = create(:conversation, contact: contact, campaign: create(:campaign))

      allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(true)

      # described class gets called in message after commit
      message = create(:message, conversation: conversation)

      expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new).with(conversation: message.conversation)
    end

    it 'doesnot calls ::MessageTemplates::Template::EmailCollect when enable_email_collect form is disabled' do
      contact = create(:contact, email: nil)
      conversation = create(:conversation, contact: contact)

      conversation.inbox.update(enable_email_collect: false)
      # ensure prechat form is enabled
      conversation.inbox.channel.update(pre_chat_form_enabled: true)
      allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(true)

      # described class gets called in message after commit
      message = create(:message, conversation: conversation)

      expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new).with(conversation: message.conversation)
    end
  end

  context 'when CSAT Survey' do
    let(:csat_survey) { double }
    let(:conversation) { create(:conversation) }

    before do
      allow(MessageTemplates::Template::CsatSurvey).to receive(:new).and_return(csat_survey)
      allow(csat_survey).to receive(:perform).and_return(true)
      create(:message, conversation: conversation, message_type: 'incoming')
    end

    it 'calls ::MessageTemplates::Template::CsatSurvey when a conversation is resolved in an inbox with survey enabled' do
      conversation.inbox.update(csat_survey_enabled: true)

      conversation.resolved!
      Conversations::ActivityMessageJob.perform_now(conversation,
                                                    { account_id: conversation.account_id, inbox_id: conversation.inbox_id, message_type: :activity,
                                                      content: 'Conversation marked resolved!!' })

      expect(MessageTemplates::Template::CsatSurvey).to have_received(:new).with(conversation: conversation)
      expect(csat_survey).to have_received(:perform)
    end

    it 'will not call ::MessageTemplates::Template::CsatSurvey when Csat is not enabled' do
      conversation.inbox.update(csat_survey_enabled: false)

      conversation.resolved!
      Conversations::ActivityMessageJob.perform_now(conversation,
                                                    { account_id: conversation.account_id, inbox_id: conversation.inbox_id, message_type: :activity,
                                                      content: 'Conversation marked resolved!!' })

      expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new).with(conversation: conversation)
      expect(csat_survey).not_to have_received(:perform)
    end

    it 'will not call ::MessageTemplates::Template::CsatSurvey if its a tweet conversation' do
      twitter_channel = create(:channel_twitter_profile)
      twitter_inbox = create(:inbox, channel: twitter_channel)
      conversation = create(:conversation, inbox: twitter_inbox, additional_attributes: { type: 'tweet' })
      conversation.inbox.update(csat_survey_enabled: true)

      conversation.resolved!
      Conversations::ActivityMessageJob.perform_now(conversation,
                                                    { account_id: conversation.account_id, inbox_id: conversation.inbox_id, message_type: :activity,
                                                      content: 'Conversation marked resolved!!' })

      expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new).with(conversation: conversation)
      expect(csat_survey).not_to have_received(:perform)
    end

    it 'will not call ::MessageTemplates::Template::CsatSurvey if another Csat was already sent' do
      conversation.inbox.update(csat_survey_enabled: true)
      conversation.messages.create!(message_type: 'outgoing', content_type: :input_csat, account: conversation.account, inbox: conversation.inbox)

      conversation.resolved!
      Conversations::ActivityMessageJob.perform_now(conversation,
                                                    { account_id: conversation.account_id, inbox_id: conversation.inbox_id, message_type: :activity,
                                                      content: 'Conversation marked resolved!!' })

      expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new).with(conversation: conversation)
      expect(csat_survey).not_to have_received(:perform)
    end
  end

  context 'when it is after working hours' do
    it 'calls ::MessageTemplates::Template::OutOfOffice' do
      contact = create(:contact)
      conversation = create(:conversation, contact: contact)

      conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
      conversation.inbox.working_hours.today.update!(closed_all_day: true)

      out_of_office_service = double

      allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
      allow(out_of_office_service).to receive(:perform).and_return(true)

      # described class gets called in message after commit
      message = create(:message, conversation: conversation)

      expect(MessageTemplates::Template::OutOfOffice).to have_received(:new).with(conversation: message.conversation)
      expect(out_of_office_service).to have_received(:perform)
    end

    it 'will not calls ::MessageTemplates::Template::OutOfOffice when outgoing message' do
      contact = create(:contact)
      conversation = create(:conversation, contact: contact)

      conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
      conversation.inbox.working_hours.today.update!(closed_all_day: true)

      out_of_office_service = double

      allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
      allow(out_of_office_service).to receive(:perform).and_return(true)

      # described class gets called in message after commit
      message = create(:message, conversation: conversation, message_type: 'outgoing')

      expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new).with(conversation: message.conversation)
      expect(out_of_office_service).not_to have_received(:perform)
    end

    it 'will not call ::MessageTemplates::Template::OutOfOffice if its a tweet conversation' do
      twitter_channel = create(:channel_twitter_profile)
      twitter_inbox = create(:inbox, channel: twitter_channel)
      twitter_inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')

      conversation = create(:conversation, inbox: twitter_inbox, additional_attributes: { type: 'tweet' })

      out_of_office_service = double

      allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
      allow(out_of_office_service).to receive(:perform).and_return(false)

      message = create(:message, conversation: conversation)
      expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new).with(conversation: message.conversation)
      expect(out_of_office_service).not_to receive(:perform)
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class MessageTemplates::Template::OutOfOffice
  pattr_initialize [:conversation!]

  def perform
    ActiveRecord::Base.transaction do
      conversation.messages.create!(out_of_office_message_params)
    end
  rescue StandardError => e
    ChatwootExceptionTracker.new(e, account: conversation.account).capture_exception
    true
  end

  private

  delegate :contact, :account, to: :conversation
  delegate :inbox, to: :message

  def out_of_office_message_params
    content = @conversation.inbox&.out_of_office_message

    {
      account_id: @conversation.account_id,
      inbox_id: @conversation.inbox_id,
      message_type: :template,
      content: content
    }
  end
end

```

Response:
require 'rails_helper'

describe MessageTemplates::Template::OutOfOffice do
  context 'when this hook is called' do
    let(:conversation) { create(:conversation) }

    it 'creates the out of office messages' do
      described_class.new(conversation: conversation).perform
      expect(conversation.messages.template.count).to eq(1)
      expect(conversation.messages.template.first.content).to eq(conversation.inbox.out_of_office_message)
    end

    it 'creates the out of office messages with template variable' do
      conversation.inbox.update!(out_of_office_message: 'Hey, {{contact.name}} we are unavailable at the moment.')
      described_class.new(conversation: conversation).perform
      expect(conversation.messages.count).to eq(1)
      expect(conversation.messages.last.content).to eq("Hey, #{conversation.contact.name} we are unavailable at the moment.")
    end

    it 'creates the out of office messages with more than one variable strings' do
      conversation.inbox.update!(out_of_office_message:
        'Hey, {{contact.name}} we are unavailable at the moment. - from {{account.name}}')
      described_class.new(conversation: conversation).perform
      expect(conversation.messages.count).to eq(1)
      expect(conversation.messages.last.content).to eq(
        "Hey, #{conversation.contact.name} we are unavailable at the moment. - from #{conversation.account.name}"
      )
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class MessageTemplates::Template::CsatSurvey
  pattr_initialize [:conversation!]

  def perform
    ActiveRecord::Base.transaction do
      conversation.messages.create!(csat_survey_message_params)
    end
  end

  private

  delegate :contact, :account, to: :conversation
  delegate :inbox, to: :message

  def csat_survey_message_params
    {
      account_id: @conversation.account_id,
      inbox_id: @conversation.inbox_id,
      message_type: :template,
      content_type: :input_csat,
      content: I18n.t('conversations.templates.csat_input_message_body')
    }
  end
end

```

Response:
require 'rails_helper'

describe MessageTemplates::Template::CsatSurvey do
  context 'when this hook is called' do
    let(:conversation) { create(:conversation) }

    it 'creates the out of office messages' do
      described_class.new(conversation: conversation).perform
      expect(conversation.messages.template.count).to eq(1)
      expect(conversation.messages.template.first.content_type).to eq('input_csat')
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class MessageTemplates::Template::EmailCollect
  pattr_initialize [:conversation!]

  def perform
    ActiveRecord::Base.transaction do
      conversation.messages.create!(ways_to_reach_you_message_params)
      conversation.messages.create!(email_input_box_template_message_params)
    end
  rescue StandardError => e
    ChatwootExceptionTracker.new(e, account: conversation.account).capture_exception
    true
  end

  private

  delegate :contact, :account, to: :conversation
  delegate :inbox, to: :message

  def ways_to_reach_you_message_params
    content = I18n.t('conversations.templates.ways_to_reach_you_message_body',
                     account_name: account.name)

    {
      account_id: @conversation.account_id,
      inbox_id: @conversation.inbox_id,
      message_type: :template,
      content: content
    }
  end

  def email_input_box_template_message_params
    content = I18n.t('conversations.templates.email_input_box_message_body',
                     account_name: account.name)

    {
      account_id: @conversation.account_id,
      inbox_id: @conversation.inbox_id,
      message_type: :template,
      content_type: :input_email,
      content: content
    }
  end
end

```

Response:
require 'rails_helper'

describe MessageTemplates::Template::EmailCollect do
  context 'when this hook is called' do
    let(:conversation) { create(:conversation) }

    it 'creates the email collect messages' do
      described_class.new(conversation: conversation).perform
      expect(conversation.messages.count).to eq(2)
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class MessageTemplates::Template::Greeting
  pattr_initialize [:conversation!]

  def perform
    ActiveRecord::Base.transaction do
      conversation.messages.create!(greeting_message_params)
    end
  rescue StandardError => e
    ChatwootExceptionTracker.new(e, account: conversation.account).capture_exception
    true
  end

  private

  delegate :contact, :account, to: :conversation
  delegate :inbox, to: :message

  def greeting_message_params
    content = @conversation.inbox&.greeting_message

    {
      account_id: @conversation.account_id,
      inbox_id: @conversation.inbox_id,
      message_type: :template,
      content: content
    }
  end
end

```

Response:
require 'rails_helper'

describe MessageTemplates::Template::Greeting do
  context 'when this hook is called' do
    let(:conversation) { create(:conversation) }

    it 'creates the email collect messages' do
      described_class.new(conversation: conversation).perform
      expect(conversation.messages.count).to eq(1)
    end

    it 'creates the greeting messages with template variable' do
      conversation.inbox.update!(greeting_message: 'Hey, {{contact.name}} welcome to our board.')
      described_class.new(conversation: conversation).perform
      expect(conversation.messages.count).to eq(1)
      expect(conversation.messages.last.content).to eq("Hey, #{conversation.contact.name} welcome to our board.")
    end

    it 'creates the greeting messages with more than one variable strings' do
      conversation.inbox.update!(greeting_message: 'Hey, {{contact.name}} welcome to our board. - from {{account.name}}')
      described_class.new(conversation: conversation).perform
      expect(conversation.messages.count).to eq(1)
      expect(conversation.messages.last.content).to eq("Hey, #{conversation.contact.name} welcome to our board. - from #{conversation.account.name}")
    end

    it 'creates the greeting messages' do
      conversation.inbox.update!(greeting_message: 'Hello welcome to our board.')
      described_class.new(conversation: conversation).perform
      expect(conversation.messages.count).to eq(1)
      expect(conversation.messages.last.content).to eq('Hello welcome to our board.')
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Instagram::ReadStatusService
  pattr_initialize [:params!]

  def perform
    return if instagram_channel.blank?

    ::Conversations::UpdateMessageStatusJob.perform_later(message.conversation.id, message.created_at) if message.present?
  end

  def instagram_id
    params[:recipient][:id]
  end

  def instagram_channel
    @instagram_channel ||= Channel::FacebookPage.find_by(instagram_id: instagram_id)
  end

  def message
    return unless params[:read][:mid]

    @message ||= @instagram_channel.inbox.messages.find_by(source_id: params[:read][:mid])
  end
end

```

Response:
require 'rails_helper'

describe Instagram::ReadStatusService do
  before do
    create(:message, message_type: :incoming, inbox: instagram_inbox, account: account, conversation: conversation,
                     source_id: 'chatwoot-app-user-id-1')
  end

  let!(:account) { create(:account) }
  let!(:instagram_channel) { create(:channel_instagram_fb_page, account: account, instagram_id: 'chatwoot-app-user-id-1') }
  let!(:instagram_inbox) { create(:inbox, channel: instagram_channel, account: account, greeting_enabled: false) }
  let!(:contact) { create(:contact, account: account) }
  let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: instagram_inbox) }
  let(:conversation) { create(:conversation, contact: contact, inbox: instagram_inbox, contact_inbox: contact_inbox) }

  describe '#perform' do
    context 'when messaging_seen callback is fired' do
      let(:message) { conversation.messages.last }

      before do
        allow(Conversations::UpdateMessageStatusJob).to receive(:perform_later)
      end

      it 'enqueues the UpdateMessageStatusJob with correct parameters if the message is found' do
        params = {
          recipient: {
            id: 'chatwoot-app-user-id-1'
          },
          read: {
            mid: message.source_id
          }
        }
        described_class.new(params: params).perform
        expect(Conversations::UpdateMessageStatusJob).to have_received(:perform_later).with(conversation.id, message.created_at)
      end

      it 'does not enqueue the UpdateMessageStatusJob if the message is not found' do
        params = {
          recipient: {
            id: 'chatwoot-app-user-id-1'
          },
          read: {
            mid: 'random-message-id'
          }
        }
        described_class.new(params: params).perform
        expect(Conversations::UpdateMessageStatusJob).not_to have_received(:perform_later)
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Instagram::SendOnInstagramService < Base::SendOnChannelService
  include HTTParty

  pattr_initialize [:message!]

  base_uri 'https://graph.facebook.com/v11.0/me'

  private

  delegate :additional_attributes, to: :contact

  def channel_class
    Channel::FacebookPage
  end

  def perform_reply
    send_to_facebook_page attachament_message_params if message.attachments.present?
    send_to_facebook_page message_params
  rescue StandardError => e
    ChatwootExceptionTracker.new(e, account: message.account, user: message.sender).capture_exception
    # TODO : handle specific errors or else page will get disconnected
    # channel.authorization_error!
  end

  def message_params
    params = {
      recipient: { id: contact.get_source_id(inbox.id) },
      message: {
        text: message.content
      }
    }

    merge_human_agent_tag(params)
  end

  def attachament_message_params
    attachment = message.attachments.first
    params = {
      recipient: { id: contact.get_source_id(inbox.id) },
      message: {
        attachment: {
          type: attachment_type(attachment),
          payload: {
            url: attachment.download_url
          }
        }
      }
    }

    merge_human_agent_tag(params)
  end

  # Deliver a message with the given payload.
  # @see https://developers.facebook.com/docs/messenger-platform/instagram/features/send-message
  def send_to_facebook_page(message_content)
    access_token = channel.page_access_token
    app_secret_proof = calculate_app_secret_proof(GlobalConfigService.load('FB_APP_SECRET', ''), access_token)
    query = { access_token: access_token }
    query[:appsecret_proof] = app_secret_proof if app_secret_proof

    # url = "https://graph.facebook.com/v11.0/me/messages?access_token=#{access_token}"

    response = HTTParty.post(
      'https://graph.facebook.com/v11.0/me/messages',
      body: message_content,
      query: query
    )

    if response[:error].present?
      Rails.logger.error("Instagram response: #{response['error']} : #{message_content}")
      message.status = :failed
      message.external_error = external_error(response)
    end

    message.source_id = response['message_id'] if response['message_id'].present?
    message.save!

    response
  end

  def external_error(response)
    # https://developers.facebook.com/docs/instagram-api/reference/error-codes/
    error_message = response[:error][:message]
    error_code = response[:error][:code]

    "#{error_code} - #{error_message}"
  end

  def calculate_app_secret_proof(app_secret, access_token)
    Facebook::Messenger::Configuration::AppSecretProofCalculator.call(
      app_secret, access_token
    )
  end

  def attachment_type(attachment)
    return attachment.file_type if %w[image audio video file].include? attachment.file_type

    'file'
  end

  def conversation_type
    conversation.additional_attributes['type']
  end

  def sent_first_outgoing_message_after_24_hours?
    # we can send max 1 message after 24 hour window
    conversation.messages.outgoing.where('id > ?', conversation.last_incoming_message.id).count == 1
  end

  def config
    Facebook::Messenger.config
  end

  def merge_human_agent_tag(params)
    global_config = GlobalConfig.get('ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT')

    return params unless global_config['ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT']

    params[:messaging_type] = 'MESSAGE_TAG'
    params[:tag] = 'HUMAN_AGENT'
    params
  end
end

```

Response:
require 'rails_helper'

describe Instagram::SendOnInstagramService do
  subject(:send_reply_service) { described_class.new(message: message) }

  before do
    stub_request(:post, /graph.facebook.com/)
    create(:message, message_type: :incoming, inbox: instagram_inbox, account: account, conversation: conversation)
  end

  let!(:account) { create(:account) }
  let!(:instagram_channel) { create(:channel_instagram_fb_page, account: account, instagram_id: 'chatwoot-app-user-id-1') }
  let!(:instagram_inbox) { create(:inbox, channel: instagram_channel, account: account, greeting_enabled: false) }
  let!(:contact) { create(:contact, account: account) }
  let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: instagram_inbox) }
  let(:conversation) { create(:conversation, contact: contact, inbox: instagram_inbox, contact_inbox: contact_inbox) }
  let(:response) { double }

  describe '#perform' do
    context 'with reply' do
      before do
        allow(Facebook::Messenger::Configuration::AppSecretProofCalculator).to receive(:call).and_return('app_secret_key', 'access_token')
        allow(HTTParty).to receive(:post).and_return(
          {
            'message_id': 'anyrandommessageid1234567890'
          }
        )
      end

      context 'without message_tag HUMAN_AGENT' do
        before do
          InstallationConfig.where(name: 'ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT').first_or_create(value: false)
        end

        it 'if message is sent from chatwoot and is outgoing' do
          message = create(:message, message_type: 'outgoing', inbox: instagram_inbox, account: account, conversation: conversation)

          allow(HTTParty).to receive(:post).with(
            {
              recipient: { id: contact.get_source_id(instagram_inbox.id) },
              message: {
                text: message.content
              }
            }
          ).and_return(
            {
              'message_id': 'anyrandommessageid1234567890'
            }
          )

          response = described_class.new(message: message).perform

          expect(response).to eq({  message_id: 'anyrandommessageid1234567890' })
        end

        it 'if message with attachment is sent from chatwoot and is outgoing' do
          message = build(:message, message_type: 'outgoing', inbox: instagram_inbox, account: account, conversation: conversation)
          attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
          attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
          message.save!
          response = described_class.new(message: message).perform

          expect(response).to eq({ message_id: 'anyrandommessageid1234567890' })
        end

        it 'if message sent from chatwoot is failed' do
          message = create(:message, message_type: 'outgoing', inbox: instagram_inbox, account: account, conversation: conversation)

          allow(HTTParty).to receive(:post).and_return(
            {
              'error': {
                'message': 'The Instagram account is restricted.',
                'type': 'OAuthException',
                'code': 400,
                'fbtrace_id': 'anyrandomfbtraceid1234567890'
              }
            }
          )
          described_class.new(message: message).perform
          expect(HTTParty).to have_received(:post)
          expect(message.reload.status).to eq('failed')
          expect(message.reload.external_error).to eq('400 - The Instagram account is restricted.')
        end
      end

      context 'with message_tag HUMAN_AGENT' do
        before do
          InstallationConfig.where(name: 'ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT').first_or_create(value: true)
        end

        it 'if message is sent from chatwoot and is outgoing' do
          message = create(:message, message_type: 'outgoing', inbox: instagram_inbox, account: account, conversation: conversation)

          allow(HTTParty).to receive(:post).with(
            {
              recipient: { id: contact.get_source_id(instagram_inbox.id) },
              message: {
                text: message.content
              },
              messaging_type: 'MESSAGE_TAG',
              tag: 'HUMAN_AGENT'
            }
          ).and_return(
            {
              'message_id': 'anyrandommessageid1234567890'
            }
          )

          described_class.new(message: message).perform
          expect(HTTParty).to have_received(:post)
        end
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# https://docs.360dialog.com/whatsapp-api/whatsapp-api/media
# https://developers.facebook.com/docs/whatsapp/api/media/

class Whatsapp::IncomingMessageService < Whatsapp::IncomingMessageBaseService
end

```

Response:
require 'rails_helper'

describe Whatsapp::IncomingMessageService do
  describe '#perform' do
    before do
      stub_request(:post, 'https://waba.360dialog.io/v1/configs/webhook')
    end

    let!(:whatsapp_channel) { create(:channel_whatsapp, sync_templates: false) }
    let(:wa_id) { '2423423243' }
    let!(:params) do
      {
        'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => wa_id }],
        'messages' => [{ 'from' => wa_id, 'id' => 'SDFADSf23sfasdafasdfa', 'text' => { 'body' => 'Test' },
                         'timestamp' => '1633034394', 'type' => 'text' }]
      }.with_indifferent_access
    end

    context 'when valid text message params' do
      it 'creates appropriate conversations, message and contacts' do
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.first.content).to eq('Test')
      end

      it 'appends to last conversation when if conversation already exists' do
        contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
        2.times.each { create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox) }
        last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        # no new conversation should be created
        expect(whatsapp_channel.inbox.conversations.count).to eq(3)
        # message appended to the last conversation
        expect(last_conversation.messages.last.content).to eq(params[:messages].first[:text][:body])
      end

      it 'reopen last conversation if last conversation is resolved and lock to single conversation is enabled' do
        whatsapp_channel.inbox.update(lock_to_single_conversation: true)
        contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
        last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
        last_conversation.update(status: 'resolved')
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        # no new conversation should be created
        expect(whatsapp_channel.inbox.conversations.count).to eq(1)
        # message appended to the last conversation
        expect(last_conversation.messages.last.content).to eq(params[:messages].first[:text][:body])
        expect(last_conversation.reload.status).to eq('open')
      end

      it 'creates a new conversation if last conversation is resolved and lock to single conversation is disabled' do
        whatsapp_channel.inbox.update(lock_to_single_conversation: false)
        contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
        last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
        last_conversation.update(status: 'resolved')
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        # new conversation should be created
        expect(whatsapp_channel.inbox.conversations.count).to eq(2)
        expect(contact_inbox.conversations.last.messages.last.content).to eq(params[:messages].first[:text][:body])
      end

      it 'will not create a new conversation if last conversation is not resolved and lock to single conversation is disabled' do
        whatsapp_channel.inbox.update(lock_to_single_conversation: false)
        contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
        last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
        last_conversation.update(status: Conversation.statuses.except('resolved').keys.sample)
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        # new conversation should be created
        expect(whatsapp_channel.inbox.conversations.count).to eq(1)
        expect(contact_inbox.conversations.last.messages.last.content).to eq(params[:messages].first[:text][:body])
      end

      it 'will not create duplicate messages when same message is received' do
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.messages.count).to eq(1)

        # this shouldn't create a duplicate message
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.messages.count).to eq(1)
      end
    end

    context 'when unsupported message types' do
      it 'ignores type ephemeral' do
        params = {
          'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => '2423423243' }],
          'messages' => [{ 'from' => '2423423243', 'id' => 'SDFADSf23sfasdafasdfa', 'text' => { 'body' => 'Test' },
                           'timestamp' => '1633034394', 'type' => 'ephemeral' }]
        }.with_indifferent_access

        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.count).to eq(0)
      end

      it 'ignores type unsupported' do
        params = {
          'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => '2423423243' }],
          'messages' => [{
            'errors' => [{ 'code': 131_051, 'title': 'Message type is currently not supported.' }],
            :from => '2423423243', :id => 'wamid.SDFADSf23sfasdafasdfa',
            :timestamp => '1667047370', :type => 'unsupported'
          }]
        }.with_indifferent_access

        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.count).to eq(0)
      end
    end

    context 'when valid status params' do
      let(:from) { '2423423243' }
      let(:contact_inbox) { create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: from) }
      let(:params) do
        {
          'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => from }],
          'messages' => [{ 'from' => from, 'id' => from, 'text' => { 'body' => 'Test' },
                           'timestamp' => '1633034394', 'type' => 'text' }]
        }.with_indifferent_access
      end

      before do
        create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
      end

      it 'update status message to read' do
        status_params = {
          'statuses' => [{ 'recipient_id' => from, 'id' => from, 'status' => 'read' }]
        }.with_indifferent_access
        message = Message.find_by!(source_id: from)
        expect(message.status).to eq('sent')
        described_class.new(inbox: whatsapp_channel.inbox, params: status_params).perform
        expect(message.reload.status).to eq('read')
      end

      it 'update status message to failed' do
        status_params = {
          'statuses' => [{ 'recipient_id' => from, 'id' => from, 'status' => 'failed',
                           'errors' => [{ 'code': 123, 'title': 'abc' }] }]
        }.with_indifferent_access

        message = Message.find_by!(source_id: from)
        expect(message.status).to eq('sent')
        described_class.new(inbox: whatsapp_channel.inbox, params: status_params).perform
        expect(message.reload.status).to eq('failed')
        expect(message.external_error).to eq('123: abc')
      end

      it 'will not throw error if unsupported status' do
        status_params = {
          'statuses' => [{ 'recipient_id' => from, 'id' => from, 'status' => 'deleted',
                           'errors' => [{ 'code': 123, 'title': 'abc' }] }]
        }.with_indifferent_access

        message = Message.find_by!(source_id: from)
        expect(message.status).to eq('sent')
        expect { described_class.new(inbox: whatsapp_channel.inbox, params: status_params).perform }.not_to raise_error
      end
    end

    context 'when valid interactive message params' do
      it 'creates appropriate conversations, message and contacts' do
        params = {
          'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => '2423423243' }],
          'messages' => [{ 'from' => '2423423243', 'id' => 'SDFADSf23sfasdafasdfa',
                           :interactive => {
                             'button_reply': {
                               'id': '1',
                               'title': 'First Button'
                             },
                             'type': 'button_reply'
                           },
                           'timestamp' => '1633034394', 'type' => 'interactive' }]
        }.with_indifferent_access
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.first.content).to eq('First Button')
      end
    end

    # ref: https://github.com/chatwoot/chatwoot/issues/3795#issuecomment-1018057318
    context 'when valid template button message params' do
      it 'creates appropriate conversations, message and contacts' do
        params = {
          'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => '2423423243' }],
          'messages' => [{ 'from' => '2423423243', 'id' => 'SDFADSf23sfasdafasdfa',
                           'button' => {
                             'text' => 'Yes this is a button'
                           },
                           'timestamp' => '1633034394', 'type' => 'button' }]
        }.with_indifferent_access
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.first.content).to eq('Yes this is a button')
      end
    end

    context 'when valid attachment message params' do
      it 'creates appropriate conversations, message and contacts' do
        stub_request(:get, whatsapp_channel.media_url('b1c68f38-8734-4ad3-b4a1-ef0c10d683')).to_return(
          status: 200,
          body: File.read('spec/assets/sample.png'),
          headers: {}
        )
        params = {
          'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => '2423423243' }],
          'messages' => [{ 'from' => '2423423243', 'id' => 'SDFADSf23sfasdafasdfa',
                           'image' => { 'id' => 'b1c68f38-8734-4ad3-b4a1-ef0c10d683',
                                        'mime_type' => 'image/jpeg',
                                        'sha256' => '29ed500fa64eb55fc19dc4124acb300e5dcca0f822a301ae99944db',
                                        'caption' => 'Check out my product!' },
                           'timestamp' => '1633034394', 'type' => 'image' }]
        }.with_indifferent_access
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.first.content).to eq('Check out my product!')
        expect(whatsapp_channel.inbox.messages.first.attachments.present?).to be true
      end
    end

    context 'when valid location message params' do
      it 'creates appropriate conversations, message and contacts' do
        params = {
          'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => '2423423243' }],
          'messages' => [{ 'from' => '2423423243', 'id' => 'SDFADSf23sfasdafasdfa',
                           'location' => { 'id' => 'b1c68f38-8734-4ad3-b4a1-ef0c10d683',
                                           :address => 'San Francisco, CA, USA',
                                           :latitude => 37.7893768,
                                           :longitude => -122.3895553,
                                           :name => 'Bay Bridge',
                                           :url => 'http://location_url.test' },
                           'timestamp' => '1633034394', 'type' => 'location' }]
        }.with_indifferent_access
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        location_attachment = whatsapp_channel.inbox.messages.first.attachments.first
        expect(location_attachment.file_type).to eq('location')
        expect(location_attachment.fallback_title).to eq('Bay Bridge, San Francisco, CA, USA')
        expect(location_attachment.coordinates_lat).to eq(37.7893768)
        expect(location_attachment.coordinates_long).to eq(-122.3895553)
        expect(location_attachment.external_url).to eq('http://location_url.test')
      end
    end

    context 'when valid contact message params' do
      it 'creates appropriate message and attachments' do
        params = { 'contacts' => [{ 'profile' => { 'name' => 'Kedar' }, 'wa_id' => '919746334593' }],
                   'messages' => [{ 'from' => '919446284490',
                                    'id' => 'wamid.SDFADSf23sfasdafasdfa',
                                    'timestamp' => '1675823265',
                                    'type' => 'contacts',
                                    'contacts' => [
                                      {
                                        'name' => { 'formatted_name' => 'Apple Inc.' },
                                        'phones' => [{ 'phone' => '+911800', 'type' => 'MAIN' }]
                                      },
                                      { 'name' => { 'first_name' => 'Chatwoot', 'formatted_name' => 'Chatwoot' },
                                        'phones' => [{ 'phone' => '+1 (415) 341-8386' }] }
                                    ] }] }.with_indifferent_access
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(Contact.all.first.name).to eq('Kedar')

        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)

        # Two messages are tested deliberately to ensure multiple contact attachments work.
        m1 = whatsapp_channel.inbox.messages.first
        contact_attachments = m1.attachments.first
        expect(m1.content).to eq('Apple Inc.')
        expect(contact_attachments.fallback_title).to eq('+911800')

        m2 = whatsapp_channel.inbox.messages.last
        contact_attachments = m2.attachments.first
        expect(m2.content).to eq('Chatwoot')
        expect(contact_attachments.fallback_title).to eq('+1 (415) 341-8386')
      end
    end

    # ref: https://github.com/chatwoot/chatwoot/issues/5840
    describe 'When the incoming waid is a brazilian number in new format with 9 included' do
      let(:wa_id) { '5541988887777' }

      it 'creates appropriate conversations, message and contacts if contact does not exit' do
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.first.content).to eq('Test')
        expect(whatsapp_channel.inbox.contact_inboxes.first.source_id).to eq(wa_id)
      end

      it 'appends to existing contact if contact inbox exists' do
        contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: wa_id)
        last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        # no new conversation should be created
        expect(whatsapp_channel.inbox.conversations.count).to eq(1)
        # message appended to the last conversation
        expect(last_conversation.messages.last.content).to eq(params[:messages].first[:text][:body])
      end
    end

    describe 'When incoming waid is a brazilian number in old format without the 9 included' do
      let(:wa_id) { '554188887777' }

      context 'when a contact inbox exists in the old format without 9 included' do
        it 'appends to existing contact' do
          contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: wa_id)
          last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
          described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
          # no new conversation should be created
          expect(whatsapp_channel.inbox.conversations.count).to eq(1)
          # message appended to the last conversation
          expect(last_conversation.messages.last.content).to eq(params[:messages].first[:text][:body])
        end
      end

      context 'when a contact inbox exists in the new format with 9 included' do
        it 'appends to existing contact' do
          contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: '5541988887777')
          last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
          described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
          # no new conversation should be created
          expect(whatsapp_channel.inbox.conversations.count).to eq(1)
          # message appended to the last conversation
          expect(last_conversation.messages.last.content).to eq(params[:messages].first[:text][:body])
        end
      end

      context 'when a contact inbox does not exist in the new format with 9 included' do
        it 'creates contact inbox with the incoming waid' do
          described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
          expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
          expect(Contact.all.first.name).to eq('Sojan Jose')
          expect(whatsapp_channel.inbox.messages.first.content).to eq('Test')
          expect(whatsapp_channel.inbox.contact_inboxes.first.source_id).to eq(wa_id)
        end
      end
    end

    describe 'when message processing is in progress' do
      it 'ignores the current message creation request' do
        params = { 'contacts' => [{ 'profile' => { 'name' => 'Kedar' }, 'wa_id' => '919746334593' }],
                   'messages' => [{ 'from' => '919446284490',
                                    'id' => 'wamid.SDFADSf23sfasdafasdfa',
                                    'timestamp' => '1675823265',
                                    'type' => 'contacts',
                                    'contacts' => [
                                      {
                                        'name' => { 'formatted_name' => 'Apple Inc.' },
                                        'phones' => [{ 'phone' => '+911800', 'type' => 'MAIN' }]
                                      },
                                      { 'name' => { 'first_name' => 'Chatwoot', 'formatted_name' => 'Chatwoot' },
                                        'phones' => [{ 'phone' => '+1 (415) 341-8386' }] }
                                    ] }] }.with_indifferent_access

        expect(Message.find_by(source_id: 'wamid.SDFADSf23sfasdafasdfa')).not_to be_present
        key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: 'wamid.SDFADSf23sfasdafasdfa')

        Redis::Alfred.setex(key, true)
        expect(Redis::Alfred.get(key)).to be_truthy

        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.messages.count).to eq(0)
        expect(Message.find_by(source_id: 'wamid.SDFADSf23sfasdafasdfa')).not_to be_present

        expect(Redis::Alfred.get(key)).to be_truthy
        Redis::Alfred.delete(key)
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Whatsapp::SendOnWhatsappService < Base::SendOnChannelService
  private

  def channel_class
    Channel::Whatsapp
  end

  def perform_reply
    should_send_template_message = template_params.present? || !message.conversation.can_reply?
    if should_send_template_message
      send_template_message
    else
      send_session_message
    end
  end

  def send_template_message
    name, namespace, lang_code, processed_parameters = processable_channel_message_template

    return if name.blank?

    message_id = channel.send_template(message.conversation.contact_inbox.source_id, {
                                         name: name,
                                         namespace: namespace,
                                         lang_code: lang_code,
                                         parameters: processed_parameters
                                       })
    message.update!(source_id: message_id) if message_id.present?
  end

  # rubocop:disable Metrics/CyclomaticComplexity
  def processable_channel_message_template
    if template_params.present?
      return [
        template_params['name'],
        template_params['namespace'],
        template_params['language'],
        template_params['processed_params']&.map { |_, value| { type: 'text', text: value } }
      ]
    end

    # Delete the following logic once the update for template_params is stable
    # see if we can match the message content to a template
    # An example template may look like "Your package has been shipped. It will be delivered in {{1}} business days.
    # We want to iterate over these templates with our message body and see if we can fit it to any of the templates
    # Then we use regex to parse the template varibles and convert them into the proper payload
    channel.message_templates&.each do |template|
      match_obj = template_match_object(template)
      next if match_obj.blank?

      # we have a match, now we need to parse the template variables and convert them into the wa recommended format
      processed_parameters = match_obj.captures.map { |x| { type: 'text', text: x } }

      # no need to look up further end the search
      return [template['name'], template['namespace'], template['language'], processed_parameters]
    end
    [nil, nil, nil, nil]
  end
  # rubocop:enable Metrics/CyclomaticComplexity

  def template_match_object(template)
    body_object = validated_body_object(template)
    return if body_object.blank?

    template_match_regex = build_template_match_regex(body_object['text'])
    message.content.match(template_match_regex)
  end

  def build_template_match_regex(template_text)
    # Converts the whatsapp template to a comparable regex string to check against the message content
    # the variables are of the format {{num}} ex:{{1}}

    # transform the template text into a regex string
    # we need to replace the {{num}} with matchers that can be used to capture the variables
    template_text = template_text.gsub(/{{\d}}/, '(.*)')
    # escape if there are regex characters in the template text
    template_text = Regexp.escape(template_text)
    # ensuring only the variables remain as capture groups
    template_text = template_text.gsub(Regexp.escape('(.*)'), '(.*)')

    template_match_string = "^#{template_text}$"
    Regexp.new template_match_string
  end

  def validated_body_object(template)
    # we don't care if its not approved template
    return if template['status'] != 'approved'

    # we only care about text body object in template. if not present we discard the template
    # we don't support other forms of templates
    template['components'].find { |obj| obj['type'] == 'BODY' && obj.key?('text') }
  end

  def send_session_message
    message_id = channel.send_message(message.conversation.contact_inbox.source_id, message)
    message.update!(source_id: message_id) if message_id.present?
  end

  def template_params
    message.additional_attributes && message.additional_attributes['template_params']
  end
end

```

Response:
require 'rails_helper'

describe Whatsapp::SendOnWhatsappService do
  template_params = {
    name: 'sample_shipping_confirmation',
    namespace: '23423423_2342423_324234234_2343224',
    language: 'en_US',
    category: 'Marketing',
    processed_params: { '1' => '3' }
  }

  describe '#perform' do
    before do
      stub_request(:post, 'https://waba.360dialog.io/v1/configs/webhook')
    end

    context 'when a valid message' do
      let(:whatsapp_request) { double }
      let!(:whatsapp_channel) { create(:channel_whatsapp, sync_templates: false) }
      let!(:contact_inbox) { create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: '123456789') }
      let!(:conversation) { create(:conversation, contact_inbox: contact_inbox, inbox: whatsapp_channel.inbox) }

      it 'calls channel.send_message when with in 24 hour limit' do
        # to handle the case of 24 hour window limit.
        create(:message, message_type: :incoming, content: 'test',
                         conversation: conversation)
        message = create(:message, message_type: :outgoing, content: 'test',
                                   conversation: conversation)
        allow(HTTParty).to receive(:post).and_return(whatsapp_request)
        allow(whatsapp_request).to receive(:success?).and_return(true)
        allow(whatsapp_request).to receive(:[]).with('messages').and_return([{ 'id' => '123456789' }])
        expect(HTTParty).to receive(:post).with(
          'https://waba.360dialog.io/v1/messages',
          headers: { 'D360-API-KEY' => 'test_key', 'Content-Type' => 'application/json' },
          body: { 'to' => '123456789', 'text' => { 'body' => 'test' }, 'type' => 'text' }.to_json
        )
        described_class.new(message: message).perform
        expect(message.reload.source_id).to eq('123456789')
      end

      it 'calls channel.send_template when after 24 hour limit' do
        message = create(:message, message_type: :outgoing, content: 'Your package has been shipped. It will be delivered in 3 business days.',
                                   conversation: conversation)
        allow(HTTParty).to receive(:post).and_return(whatsapp_request)
        allow(whatsapp_request).to receive(:success?).and_return(true)
        allow(whatsapp_request).to receive(:[]).with('messages').and_return([{ 'id' => '123456789' }])
        expect(HTTParty).to receive(:post).with(
          'https://waba.360dialog.io/v1/messages',
          headers: { 'D360-API-KEY' => 'test_key', 'Content-Type' => 'application/json' },
          body: {
            to: '123456789',
            template: {
              name: 'sample_shipping_confirmation',
              namespace: '23423423_2342423_324234234_2343224',
              language: { 'policy': 'deterministic', 'code': 'en_US' },
              components: [{ 'type': 'body', 'parameters': [{ 'type': 'text', 'text': '3' }] }]
            },
            type: 'template'
          }.to_json
        )
        described_class.new(message: message).perform
        expect(message.reload.source_id).to eq('123456789')
      end

      it 'calls channel.send_template if template_params are present' do
        message = create(:message, additional_attributes: { template_params: template_params },
                                   content: 'Your package will be delivered in 3 business days.', conversation: conversation, message_type: :outgoing)
        allow(HTTParty).to receive(:post).and_return(whatsapp_request)
        allow(whatsapp_request).to receive(:success?).and_return(true)
        allow(whatsapp_request).to receive(:[]).with('messages').and_return([{ 'id' => '123456789' }])
        expect(HTTParty).to receive(:post).with(
          'https://waba.360dialog.io/v1/messages',
          headers: { 'D360-API-KEY' => 'test_key', 'Content-Type' => 'application/json' },
          body: {
            to: '123456789',
            template: {
              name: 'sample_shipping_confirmation',
              namespace: '23423423_2342423_324234234_2343224',
              language: { 'policy': 'deterministic', 'code': 'en_US' },
              components: [{ 'type': 'body', 'parameters': [{ 'type': 'text', 'text': '3' }] }]
            },
            type: 'template'
          }.to_json
        )
        described_class.new(message: message).perform
        expect(message.reload.source_id).to eq('123456789')
      end

      it 'calls channel.send_template when template has regexp characters' do
        message = create(
          :message,
          message_type: :outgoing,
          content: 'عميلنا العزيز الرجاء الرد على هذه الرسالة بكلمة *نعم* للرد على إستفساركم من قبل خدمة العملاء.',
          conversation: conversation
        )
        allow(HTTParty).to receive(:post).and_return(whatsapp_request)
        allow(whatsapp_request).to receive(:success?).and_return(true)
        allow(whatsapp_request).to receive(:[]).with('messages').and_return([{ 'id' => '123456789' }])
        expect(HTTParty).to receive(:post).with(
          'https://waba.360dialog.io/v1/messages',
          headers: { 'D360-API-KEY' => 'test_key', 'Content-Type' => 'application/json' },
          body: {
            to: '123456789',
            template: {
              name: 'customer_yes_no',
              namespace: '2342384942_32423423_23423fdsdaf23',
              language: { 'policy': 'deterministic', 'code': 'ar' },
              components: [{ 'type': 'body', 'parameters': [] }]
            },
            type: 'template'
          }.to_json
        )
        described_class.new(message: message).perform
        expect(message.reload.source_id).to eq('123456789')
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# https://docs.360dialog.com/whatsapp-api/whatsapp-api/media
# https://developers.facebook.com/docs/whatsapp/api/media/

class Whatsapp::IncomingMessageWhatsappCloudService < Whatsapp::IncomingMessageBaseService
  private

  def processed_params
    @processed_params ||= params[:entry].try(:first).try(:[], 'changes').try(:first).try(:[], 'value')
  end

  def download_attachment_file(attachment_payload)
    url_response = HTTParty.get(inbox.channel.media_url(attachment_payload[:id]), headers: inbox.channel.api_headers)
    # This url response will be failure if the access token has expired.
    inbox.channel.authorization_error! if url_response.unauthorized?
    Down.download(url_response.parsed_response['url'], headers: inbox.channel.api_headers) if url_response.success?
  end
end

```

Response:
require 'rails_helper'

describe Whatsapp::IncomingMessageWhatsappCloudService do
  describe '#perform' do
    let!(:whatsapp_channel) { create(:channel_whatsapp, provider: 'whatsapp_cloud', sync_templates: false, validate_provider_config: false) }
    let(:params) do
      {
        phone_number: whatsapp_channel.phone_number,
        object: 'whatsapp_business_account',
        entry: [{
          changes: [{
            value: {
              contacts: [{ profile: { name: 'Sojan Jose' }, wa_id: '2423423243' }],
              messages: [{
                from: '2423423243',
                image: {
                  id: 'b1c68f38-8734-4ad3-b4a1-ef0c10d683',
                  mime_type: 'image/jpeg',
                  sha256: '29ed500fa64eb55fc19dc4124acb300e5dcca0f822a301ae99944db',
                  caption: 'Check out my product!'
                },
                timestamp: '1664799904', type: 'image'
              }]
            }
          }]
        }]
      }.with_indifferent_access
    end

    context 'when valid attachment message params' do
      it 'creates appropriate conversations, message and contacts' do
        stub_request(:get, whatsapp_channel.media_url('b1c68f38-8734-4ad3-b4a1-ef0c10d683')).to_return(
          status: 200,
          body: {
            messaging_product: 'whatsapp',
            url: 'https://chatwoot-assets.local/sample.png',
            mime_type: 'image/jpeg',
            sha256: 'sha256',
            file_size: 'SIZE',
            id: 'b1c68f38-8734-4ad3-b4a1-ef0c10d683'
          }.to_json,
          headers: { 'content-type' => 'application/json' }
        )
        stub_request(:get, 'https://chatwoot-assets.local/sample.png').to_return(
          status: 200,
          body: File.read('spec/assets/sample.png')
        )

        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.first.content).to eq('Check out my product!')
        expect(whatsapp_channel.inbox.messages.first.attachments.present?).to be true
      end

      it 'increments reauthorization count if fetching attachment fails' do
        stub_request(:get, whatsapp_channel.media_url('b1c68f38-8734-4ad3-b4a1-ef0c10d683')).to_return(
          status: 401
        )

        described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.first.content).to eq('Check out my product!')
        expect(whatsapp_channel.inbox.messages.first.attachments.present?).to be false
        expect(whatsapp_channel.authorization_error_count).to eq(1)
      end
    end

    context 'when invalid attachment message params' do
      let(:error_params) do
        {
          phone_number: whatsapp_channel.phone_number,
          object: 'whatsapp_business_account',
          entry: [{
            changes: [{
              value: {
                contacts: [{ profile: { name: 'Sojan Jose' }, wa_id: '2423423243' }],
                messages: [{
                  from: '2423423243',
                  image: {
                    id: 'b1c68f38-8734-4ad3-b4a1-ef0c10d683',
                    mime_type: 'image/jpeg',
                    sha256: '29ed500fa64eb55fc19dc4124acb300e5dcca0f822a301ae99944db',
                    caption: 'Check out my product!'
                  },
                  errors: [{
                    code: 400,
                    details: 'Last error was: ServerThrottle. Http request error: HTTP response code said error. See logs for details',
                    title: 'Media download failed: Not retrying as download is not retriable at this time'
                  }],
                  timestamp: '1664799904', type: 'image'
                }]
              }
            }]
          }]
        }.with_indifferent_access
      end

      it 'with attachment errors' do
        described_class.new(inbox: whatsapp_channel.inbox, params: error_params).perform
        expect(whatsapp_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('Sojan Jose')
        expect(whatsapp_channel.inbox.messages.count).to eq(0)
      end
    end

    context 'when invalid params' do
      it 'will not throw error' do
        described_class.new(inbox: whatsapp_channel.inbox, params: { phone_number: whatsapp_channel.phone_number,
                                                                     object: 'whatsapp_business_account', entry: {} }).perform
        expect(whatsapp_channel.inbox.conversations.count).to eq(0)
        expect(Contact.all.first).to be_nil
        expect(whatsapp_channel.inbox.messages.count).to eq(0)
      end
    end
  end
end