Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

# Consolidate notifications based on a threshold and a time window.
#
# If a consolidated notification already exists, we'll update it instead.
# If it doesn't and creating a new one would match the threshold, we delete existing ones and create a consolidated one.
# Otherwise, save the original one.
#
# Constructor arguments:
#
# - from: The notification type of the unconsolidated notification. e.g. `Notification.types[:private_message]`
# - to: The type the consolidated notification will have. You can use the same value as from to flatten notifications or bump existing ones.
# - threshold: If creating a new notification would match this number, we'll destroy existing ones and create a consolidated one. It also accepts a lambda that returns a number.
# - consolidation_window: Only consolidate notifications created since this value (Pass a ActiveSupport::Duration instance, and we'll call #ago on it).
# - unconsolidated_query_blk: A block with additional queries to apply when fetching for unconsolidated notifications.
# - consolidated_query_blk: A block with additional queries to apply when fetching for a consolidated notification.
#
# Need to call #set_precondition to configure this:
#
# - precondition_blk: A block that receives the mutated data and returns true if we have everything we need to consolidate.
#
# Need to call #set_mutations to configure this:
#
# - set_data_blk: A block that receives the notification data hash and mutates it, adding additional data needed for consolidation.
#
# Need to call #before_consolidation_callbacks to configure this:
#
# - before_update_blk: A block that is called before updating an already consolidated notification.
#                      Receives the consolidated object, the data hash, and the original notification.
#
# - before_consolidation_blk: A block that is called before creating a consolidated object.
#                             Receives an ActiveRecord::Relation with notifications about to be consolidated, and the new data hash.
#

module Notifications
  class ConsolidateNotifications < ConsolidationPlan
    def initialize(
      from:,
      to:,
      consolidation_window: nil,
      unconsolidated_query_blk: nil,
      consolidated_query_blk: nil,
      threshold:
    )
      @from = from
      @to = to
      @threshold = threshold
      @consolidation_window = consolidation_window
      @consolidated_query_blk = consolidated_query_blk
      @unconsolidated_query_blk = unconsolidated_query_blk
      @precondition_blk = nil
      @set_data_blk = nil
      @bump_notification = bump_notification
    end

    def before_consolidation_callbacks(before_update_blk: nil, before_consolidation_blk: nil)
      @before_update_blk = before_update_blk
      @before_consolidation_blk = before_consolidation_blk
      self
    end

    def can_consolidate_data?(notification)
      return false if get_threshold.zero? || to.blank?
      return false if notification.notification_type != from

      @data = consolidated_data(notification)

      return true if @precondition_blk.nil?
      @precondition_blk.call(data, notification)
    end

    def consolidate_or_save!(notification)
      @data ||= consolidated_data(notification)
      return unless can_consolidate_data?(notification)

      update_consolidated_notification!(notification) ||
        create_consolidated_notification!(notification) || notification.tap(&:save!)
    end

    private

    attr_reader(
      :notification,
      :from,
      :to,
      :data,
      :threshold,
      :consolidated_query_blk,
      :unconsolidated_query_blk,
      :consolidation_window,
      :bump_notification,
    )

    def update_consolidated_notification!(notification)
      notifications = user_notifications(notification, to)

      if consolidated_query_blk.present?
        notifications = consolidated_query_blk.call(notifications, data)
      end
      consolidated = notifications.first
      return if consolidated.blank?

      data_hash = consolidated.data_hash.merge(data)
      data_hash[:count] += 1 if data_hash[:count].present?

      @before_update_blk.call(consolidated, data_hash, notification) if @before_update_blk

      # Hack: We don't want to cache the old data if we're about to update it.
      consolidated.instance_variable_set(:@data_hash, nil)

      consolidated.update!(data: data_hash.to_json, read: false, updated_at: timestamp)

      consolidated
    end

    def create_consolidated_notification!(notification)
      notifications = user_notifications(notification, from)
      if unconsolidated_query_blk.present?
        notifications = unconsolidated_query_blk.call(notifications, data)
      end

      # Saving the new notification would pass the threshold? Consolidate instead.
      count_after_saving_notification = notifications.count + 1
      return if count_after_saving_notification <= get_threshold

      timestamp = notifications.last.created_at
      data[:count] = count_after_saving_notification

      @before_consolidation_blk.call(notifications, data) if @before_consolidation_blk

      consolidated = nil

      Notification.transaction do
        notifications.destroy_all

        consolidated =
          Notification.create!(
            notification_type: to,
            user_id: notification.user_id,
            data: data.to_json,
            updated_at: timestamp,
            created_at: timestamp,
          )
      end

      consolidated
    end

    def get_threshold
      threshold.is_a?(Proc) ? threshold.call : threshold
    end

    def user_notifications(notification, type)
      notifications = super(notification, type)

      if consolidation_window.present?
        notifications = notifications.where("created_at > ?", consolidation_window.ago)
      end

      notifications
    end

    def timestamp
      @timestamp ||= Time.zone.now
    end
  end
end

```

Response:
# frozen_string_literal: true

RSpec.describe Notifications::ConsolidateNotifications do
  describe "#before_consolidation_callbacks" do
    fab!(:user)
    let(:rule) do
      described_class.new(
        from: Notification.types[:liked],
        to: Notification.types[:liked],
        consolidation_window: 10.minutes,
        consolidated_query_blk:
          Proc.new { |notifications| notifications.where("(data::json ->> 'consolidated')::bool") },
        threshold: 1,
      ).set_mutations(set_data_blk: Proc.new { |n| n.data_hash.merge(consolidated: true) })
    end

    it "applies a callback when consolidating a notification" do
      rule.before_consolidation_callbacks(
        before_consolidation_blk:
          Proc.new { |_, data| data[:consolidation_callback_called] = true },
      )

      rule.consolidate_or_save!(build_like_notification)
      rule.consolidate_or_save!(build_like_notification)

      consolidated_notification = Notification.where(user: user).last

      expect(consolidated_notification.data_hash[:consolidation_callback_called]).to eq(true)
    end

    it "applies a callback when updating a consolidated notification" do
      rule.before_consolidation_callbacks(
        before_update_blk: Proc.new { |_, data| data[:update_callback_called] = true },
      )

      rule.consolidate_or_save!(build_like_notification)
      rule.consolidate_or_save!(build_like_notification)

      consolidated_notification = Notification.where(user: user).last

      expect(consolidated_notification.data_hash[:update_callback_called]).to be_nil

      rule.consolidate_or_save!(build_like_notification)

      consolidated_notification = Notification.where(user: user).last

      expect(consolidated_notification.data_hash[:update_callback_called]).to eq(true)
    end

    def build_like_notification
      Fabricate.build(
        :notification,
        user: user,
        notification_type: Notification.types[:liked],
        data: {}.to_json,
      )
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true

module Notifications
  class ConsolidationPlanner
    def consolidate_or_save!(notification)
      plan = plan_for(notification)
      return :no_plan if plan.nil?

      plan.consolidate_or_save!(notification)
    end

    private

    def plan_for(notification)
      consolidation_plans = [
        liked_by_two_users,
        liked,
        group_message_summary,
        group_membership,
        new_features_notification,
      ]
      consolidation_plans.concat(DiscoursePluginRegistry.notification_consolidation_plans)

      consolidation_plans.detect { |plan| plan.can_consolidate_data?(notification) }
    end

    def liked
      ConsolidateNotifications
        .new(
          from: Notification.types[:liked],
          to: Notification.types[:liked_consolidated],
          threshold: -> { SiteSetting.notification_consolidation_threshold },
          consolidation_window: SiteSetting.likes_notification_consolidation_window_mins.minutes,
          unconsolidated_query_blk:
            Proc.new do |notifications, data|
              key = "display_username"
              value = data[key.to_sym]
              filtered = notifications.where("data::json ->> 'username2' IS NULL")

              filtered = filtered.where("data::json ->> '#{key}' = ?", value) if value

              filtered
            end,
          consolidated_query_blk: filtered_by_data_attribute("display_username"),
        )
        .set_mutations(
          set_data_blk:
            Proc.new do |notification|
              data = notification.data_hash
              data.merge(username: data[:display_username])
            end,
        )
        .set_precondition(precondition_blk: Proc.new { |data| data[:username2].blank? })
    end

    def liked_by_two_users
      DeletePreviousNotifications
        .new(
          type: Notification.types[:liked],
          previous_query_blk:
            Proc.new do |notifications, data|
              notifications.where(id: data[:previous_notification_id])
            end,
        )
        .set_mutations(
          set_data_blk:
            Proc.new do |notification|
              existing_notification_of_same_type =
                Notification
                  .where(user: notification.user)
                  .order("notifications.id DESC")
                  .where(topic_id: notification.topic_id, post_number: notification.post_number)
                  .where(notification_type: notification.notification_type)
                  .where("created_at > ?", 1.day.ago)
                  .first

              data = notification.data_hash
              if existing_notification_of_same_type
                same_type_data = existing_notification_of_same_type.data_hash
                data.merge(
                  previous_notification_id: existing_notification_of_same_type.id,
                  username2: same_type_data[:display_username],
                  count: (same_type_data[:count] || 1).to_i + 1,
                )
              else
                data
              end
            end,
        )
        .set_precondition(
          precondition_blk:
            Proc.new do |data, notification|
              always_freq = UserOption.like_notification_frequency_type[:always]

              notification.user&.user_option&.like_notification_frequency == always_freq &&
                data[:previous_notification_id].present?
            end,
        )
    end

    def group_membership
      ConsolidateNotifications
        .new(
          from: Notification.types[:private_message],
          to: Notification.types[:membership_request_consolidated],
          threshold: -> { SiteSetting.notification_consolidation_threshold },
          consolidation_window: Notification::MEMBERSHIP_REQUEST_CONSOLIDATION_WINDOW_HOURS.hours,
          unconsolidated_query_blk: filtered_by_data_attribute("topic_title"),
          consolidated_query_blk: filtered_by_data_attribute("group_name"),
        )
        .set_precondition(precondition_blk: Proc.new { |data| data[:group_name].present? })
        .set_mutations(
          set_data_blk:
            Proc.new do |notification|
              data = notification.data_hash
              post_id = data[:original_post_id]
              custom_field =
                PostCustomField.select(:value).find_by(post_id: post_id, name: "requested_group_id")
              group_id = custom_field&.value
              group_name =
                group_id.present? ? Group.select(:name).find_by(id: group_id.to_i)&.name : nil

              data[:group_name] = group_name
              data
            end,
        )
    end

    def group_message_summary
      DeletePreviousNotifications.new(
        type: Notification.types[:group_message_summary],
        previous_query_blk: filtered_by_data_attribute("group_id"),
      ).set_precondition(precondition_blk: Proc.new { |data| data[:group_id].present? })
    end

    def filtered_by_data_attribute(attribute_name)
      Proc.new do |notifications, data|
        if (value = data[attribute_name.to_sym])
          notifications.where("data::json ->> '#{attribute_name}' = ?", value.to_s)
        else
          notifications
        end
      end
    end

    def new_features_notification
      DeletePreviousNotifications.new(type: Notification.types[:new_features])
    end
  end
end

```

Response:
# frozen_string_literal: true

RSpec.describe Notifications::ConsolidationPlanner do
  subject(:planner) { described_class.new }

  describe "#consolidate_or_save!" do
    let(:threshold) { 1 }
    fab!(:user)
    let(:like_user) { "user1" }

    before { SiteSetting.notification_consolidation_threshold = threshold }

    it "does nothing it haven't passed the consolidation threshold yet" do
      notification = build_notification(:liked, { display_username: like_user })

      saved_like = planner.consolidate_or_save!(notification)

      expect(saved_like.id).to be_present
      expect(saved_like.notification_type).to eq(Notification.types[:liked])
    end

    it "consolidates multiple notifications into a new one" do
      first_notification =
        Fabricate(
          :notification,
          user: user,
          notification_type: Notification.types[:liked],
          data: { display_username: like_user }.to_json,
        )
      notification = build_notification(:liked, { display_username: like_user })

      consolidated_like = planner.consolidate_or_save!(notification)

      expect(consolidated_like.id).not_to eq(first_notification.id)
      expect(consolidated_like.notification_type).to eq(Notification.types[:liked_consolidated])
      data = JSON.parse(consolidated_like.data)
      expect(data["count"]).to eq(threshold + 1)
    end

    it "updates the notification if we already consolidated it" do
      count = 5
      Fabricate(
        :notification,
        user: user,
        notification_type: Notification.types[:liked_consolidated],
        data: { count: count, display_username: like_user }.to_json,
      )
      notification = build_notification(:liked, { display_username: like_user })

      updated = planner.consolidate_or_save!(notification)

      expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
      data = JSON.parse(updated.data)
      expect(data["count"]).to eq(count + 1)
    end
  end

  def build_notification(type_sym, data)
    Fabricate.build(
      :notification,
      user: user,
      notification_type: Notification.types[type_sym],
      data: data.to_json,
    )
  end

  def plan_for(notification)
    planner.plan_for(notification)
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class SearchService
  pattr_initialize [:current_user!, :current_account!, :params!, :search_type!]

  def perform
    case search_type
    when 'Message'
      { messages: filter_messages }
    when 'Conversation'
      { conversations: filter_conversations }
    when 'Contact'
      { contacts: filter_contacts }
    else
      { contacts: filter_contacts, messages: filter_messages, conversations: filter_conversations }
    end
  end

  private

  def accessable_inbox_ids
    @accessable_inbox_ids ||= @current_user.assigned_inboxes.pluck(:id)
  end

  def search_query
    @search_query ||= params[:q].to_s.strip
  end

  def filter_conversations
    @conversations = current_account.conversations.where(inbox_id: accessable_inbox_ids)
                                    .joins('INNER JOIN contacts ON conversations.contact_id = contacts.id')
                                    .where("cast(conversations.display_id as text) ILIKE :search OR contacts.name ILIKE :search OR contacts.email
                            ILIKE :search OR contacts.phone_number ILIKE :search OR contacts.identifier ILIKE :search", search: "%#{search_query}%")
                                    .order('conversations.created_at DESC')
                                    .limit(10)
  end

  def filter_messages
    @messages = current_account.messages.where(inbox_id: accessable_inbox_ids)
                               .where('messages.content ILIKE :search', search: "%#{search_query}%")
                               .where('created_at >= ?', 3.months.ago)
                               .reorder('created_at DESC')
                               .limit(10)
  end

  def filter_contacts
    @contacts = current_account.contacts.where(
      "name ILIKE :search OR email ILIKE :search OR phone_number
      ILIKE :search OR identifier ILIKE :search", search: "%#{search_query}%"
    ).resolved_contacts.order_on_last_activity_at('desc').limit(10)
  end
end

```

Response:
require 'rails_helper'

describe SearchService do
  subject(:search) { described_class.new(current_user: user, current_account: account, params: params, search_type: search_type) }

  let(:search_type) { 'all' }
  let!(:account) { create(:account) }
  let!(:user) { create(:user, account: account) }
  let!(:inbox) { create(:inbox, account: account, enable_auto_assignment: false) }
  let!(:harry) { create(:contact, name: 'Harry Potter', email: 'test@test.com', account_id: account.id) }
  let!(:conversation) { create(:conversation, contact: harry, inbox: inbox, account: account) }
  let!(:message) { create(:message, account: account, inbox: inbox, content: 'Harry Potter is a wizard') }

  before do
    create(:inbox_member, user: user, inbox: inbox)
    Current.account = account
  end

  after do
    Current.account = nil
  end

  describe '#perform' do
    context 'when search types' do
      let(:params) { { q: 'Potter' } }

      it 'returns all for all' do
        search_type = 'all'
        search = described_class.new(current_user: user, current_account: account, params: params, search_type: search_type)
        expect(search.perform.keys).to match_array(%i[contacts messages conversations])
      end

      it 'returns contacts for contacts' do
        search_type = 'Contact'
        search = described_class.new(current_user: user, current_account: account, params: params, search_type: search_type)
        expect(search.perform.keys).to match_array(%i[contacts])
      end

      it 'returns messages for messages' do
        search_type = 'Message'
        search = described_class.new(current_user: user, current_account: account, params: params, search_type: search_type)
        expect(search.perform.keys).to match_array(%i[messages])
      end

      it 'returns conversations for conversations' do
        search_type = 'Conversation'
        search = described_class.new(current_user: user, current_account: account, params: params, search_type: search_type)
        expect(search.perform.keys).to match_array(%i[conversations])
      end
    end

    context 'when contact search' do
      it 'searches across name, email, phone_number and identifier and returns in the order of contact last_activity_at' do
        # random contact
        create(:contact, account_id: account.id)
        # unresolved contact -> no identifying info
        # will not appear in search results
        create(:contact, name: 'Harry Potter', account_id: account.id)
        harry2 = create(:contact, email: 'HarryPotter@test.com', account_id: account.id, last_activity_at: 2.days.ago)
        harry3 = create(:contact, identifier: 'Potter123', account_id: account.id, last_activity_at: 1.day.ago)
        harry4 = create(:contact, identifier: 'Potter1235', account_id: account.id, last_activity_at: 2.minutes.ago)

        params = { q: 'Potter ' }
        search = described_class.new(current_user: user, current_account: account, params: params, search_type: 'Contact')
        expect(search.perform[:contacts].map(&:id)).to eq([harry4.id, harry3.id, harry2.id, harry.id])
      end
    end

    context 'when message search' do
      it 'searches across message content and return in created_at desc' do
        # random messages in another account
        create(:message, content: 'Harry Potter is a wizard')
        # random messsage in inbox with out access
        create(:message, account: account, inbox: create(:inbox, account: account), content: 'Harry Potter is a wizard')
        message2 = create(:message, account: account, inbox: inbox, content: 'harry is cool')
        params = { q: 'Harry' }
        search = described_class.new(current_user: user, current_account: account, params: params, search_type: 'Message')
        expect(search.perform[:messages].map(&:id)).to eq([message2.id, message.id])
      end
    end

    context 'when conversation search' do
      it 'searches across conversations using contact information and order by created_at desc' do
        # random messages in another inbox
        random = create(:contact, account_id: account.id)
        create(:conversation, contact: random, inbox: inbox, account: account)
        conv2 = create(:conversation, contact: harry, inbox: inbox, account: account)
        params = { q: 'Harry' }
        search = described_class.new(current_user: user, current_account: account, params: params, search_type: 'Conversation')
        expect(search.perform[:conversations].map(&:id)).to eq([conv2.id, conversation.id])
      end

      it 'searches across conversations with display id' do
        random = create(:contact, account_id: account.id, name: 'random', email: 'random@random.test', identifier: 'random')
        new_converstion = create(:conversation, contact: random, inbox: inbox, account: account)
        params = { q: new_converstion.display_id }
        search = described_class.new(current_user: user, current_account: account, params: params, search_type: 'Conversation')
        expect(search.perform[:conversations].map(&:id)).to include new_converstion.id
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class ActionService
  include EmailHelper

  def initialize(conversation)
    @conversation = conversation.reload
  end

  def mute_conversation(_params)
    @conversation.mute!
  end

  def snooze_conversation(_params)
    @conversation.snoozed!
  end

  def resolve_conversation(_params)
    @conversation.resolved!
  end

  def change_status(status)
    @conversation.update!(status: status[0])
  end

  def change_priority(priority)
    @conversation.update!(priority: (priority[0] == 'nil' ? nil : priority[0]))
  end

  def add_label(labels)
    return if labels.empty?

    @conversation.reload.add_labels(labels)
  end

  def assign_agent(agent_ids = [])
    return @conversation.update!(assignee_id: nil) if agent_ids[0] == 'nil'

    return unless agent_belongs_to_inbox?(agent_ids)

    @agent = @account.users.find_by(id: agent_ids)

    @conversation.update!(assignee_id: @agent.id) if @agent.present?
  end

  def remove_label(labels)
    return if labels.empty?

    labels = @conversation.label_list - labels
    @conversation.update(label_list: labels)
  end

  def assign_team(team_ids = [])
    return unassign_team if team_ids[0].zero?
    return unless team_belongs_to_account?(team_ids)

    @conversation.update!(team_id: team_ids[0])
  end

  def remove_assigned_team(_params)
    @conversation.update!(team_id: nil)
  end

  def send_email_transcript(emails)
    emails = emails[0].gsub(/\s+/, '').split(',')

    emails.each do |email|
      email = parse_email_variables(@conversation, email)
      ConversationReplyMailer.with(account: @conversation.account).conversation_transcript(@conversation, email)&.deliver_later
    end
  end

  private

  def agent_belongs_to_inbox?(agent_ids)
    member_ids = @conversation.inbox.members.pluck(:user_id)
    assignable_agent_ids = member_ids + @account.administrators.ids

    assignable_agent_ids.include?(agent_ids[0])
  end

  def team_belongs_to_account?(team_ids)
    @account.team_ids.include?(team_ids[0])
  end

  def conversation_a_tweet?
    return false if @conversation.additional_attributes.blank?

    @conversation.additional_attributes['type'] == 'tweet'
  end
end

```

Response:
require 'rails_helper'

describe ActionService do
  let(:account) { create(:account) }

  describe '#resolve_conversation' do
    let(:conversation) { create(:conversation) }
    let(:action_service) { described_class.new(conversation) }

    it 'resolves the conversation' do
      expect(conversation.status).to eq('open')
      action_service.resolve_conversation(nil)
      expect(conversation.reload.status).to eq('resolved')
    end
  end

  describe '#change_priority' do
    let(:conversation) { create(:conversation) }
    let(:action_service) { described_class.new(conversation) }

    it 'changes the priority of the conversation to medium' do
      action_service.change_priority(['medium'])
      expect(conversation.reload.priority).to eq('medium')
    end

    it 'changes the priority of the conversation to nil' do
      action_service.change_priority(['nil'])
      expect(conversation.reload.priority).to be_nil
    end
  end

  describe '#assign_agent' do
    let(:agent) { create(:user, account: account, role: :agent) }
    let(:conversation) { create(:conversation, account: account) }
    let(:inbox_member) { create(:inbox_member, inbox: conversation.inbox, user: agent) }
    let(:action_service) { described_class.new(conversation) }

    it 'unassigns the conversation if agent id is nil' do
      action_service.assign_agent(['nil'])
      expect(conversation.reload.assignee).to be_nil
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Twitter::WebhookSubscribeService
  include Rails.application.routes.url_helpers

  pattr_initialize [:inbox_id]

  def perform
    ensure_webhook
    unless subscription?
      subscribe_response = twitter_client.create_subscription
      raise StandardError, 'Twitter Subscription Failed' unless subscribe_response.status == '204'
    end

    true
  end

  private

  delegate :channel, to: :inbox
  delegate :twitter_client, to: :channel

  def inbox
    Inbox.find(inbox_id)
  end

  def twitter_url
    webhooks_twitter_url(protocol: 'https')
  end

  def ensure_webhook
    webhooks = fetch_webhooks
    return true if webhooks&.first&.try(:[], 'url') == twitter_url

    # twitter supports only one webhook url per environment
    # so we will delete the existing one if its not chatwoot
    unregister_webhook(webhooks.first) if webhooks&.first
    register_webhook
  end

  def unregister_webhook(webhook)
    unregister_response = twitter_client.unregister_webhook(id: webhook.try(:[], 'id'))
    Rails.logger.info "TWITTER_UNREGISTER_WEBHOOK: #{unregister_response.body}"
  end

  def register_webhook
    register_response = twitter_client.register_webhook(url: twitter_url)
    Rails.logger.info "TWITTER_REGISTER_WEBHOOK: #{register_response.body}"
  end

  def subscription?
    response = twitter_client.fetch_subscriptions
    response.status == '204'
  end

  def fetch_webhooks
    twitter_client.fetch_webhooks.body
  end
end

```

Response:
require 'rails_helper'

describe Twitter::WebhookSubscribeService do
  subject(:webhook_subscribe_service) { described_class.new(inbox_id: twitter_inbox.id) }

  let(:twitter_client) { instance_double(Twitty::Facade) }
  let(:twitter_success_response) { instance_double(Twitty::Response, status: '200', body: { message: 'Valid' }) }
  let(:twitter_error_response) { instance_double(Twitty::Response, status: '422', body: { message: 'Invalid request' }) }
  let(:account) { create(:account) }
  let(:twitter_channel) { create(:channel_twitter_profile, account: account) }
  let(:twitter_inbox) { create(:inbox, channel: twitter_channel, account: account) }

  before do
    allow(Twitty::Facade).to receive(:new).and_return(twitter_client)
    allow(twitter_client).to receive(:register_webhook).and_return(twitter_success_response)
    allow(twitter_client).to receive(:unregister_webhook).and_return(twitter_success_response)
    allow(twitter_client).to receive(:fetch_subscriptions).and_return(instance_double(Twitty::Response, status: '204', body: { message: 'Valid' }))
    allow(twitter_client).to receive(:create_subscription).and_return(instance_double(Twitty::Response, status: '204', body: { message: 'Valid' }))
  end

  describe '#perform' do
    context 'when webhook is not registered' do
      it 'calls register_webhook' do
        allow(twitter_client).to receive(:fetch_webhooks).and_return(
          instance_double(Twitty::Response, status: '200', body: {})
        )
        webhook_subscribe_service.perform
        expect(twitter_client).not_to have_received(:unregister_webhook)
        expect(twitter_client).to have_received(:register_webhook)
      end
    end

    context 'when valid webhook is registered' do
      it 'calls unregister_webhook and then register webhook' do
        allow(twitter_client).to receive(:fetch_webhooks).and_return(
          instance_double(Twitty::Response, status: '200',
                                            body: [{ 'url' => webhook_subscribe_service.send(:twitter_url) }])
        )
        webhook_subscribe_service.perform
        expect(twitter_client).not_to have_received(:unregister_webhook)
        expect(twitter_client).not_to have_received(:register_webhook)
      end
    end

    context 'when invalid webhook is registered' do
      it 'calls unregister_webhook and then register webhook' do
        allow(twitter_client).to receive(:fetch_webhooks).and_return(
          instance_double(Twitty::Response, status: '200',
                                            body: [{ 'url' => 'invalid_url' }])
        )
        webhook_subscribe_service.perform
        expect(twitter_client).to have_received(:unregister_webhook)
        expect(twitter_client).to have_received(:register_webhook)
      end
    end

    context 'when correct webhook is present' do
      it 'calls create subscription if subscription is not present' do
        allow(twitter_client).to receive(:fetch_webhooks).and_return(
          instance_double(Twitty::Response, status: '200',
                                            body: [{ 'url' => webhook_subscribe_service.send(:twitter_url) }])
        )
        allow(twitter_client).to receive(:fetch_subscriptions).and_return(instance_double(Twitty::Response, status: '500'))
        webhook_subscribe_service.perform
        expect(twitter_client).to have_received(:create_subscription)
      end

      it 'does not call create subscription if subscription is already present' do
        allow(twitter_client).to receive(:fetch_webhooks).and_return(
          instance_double(Twitty::Response, status: '200',
                                            body: [{ 'url' => webhook_subscribe_service.send(:twitter_url) }])
        )
        webhook_subscribe_service.perform
        expect(twitter_client).not_to have_received(:create_subscription)
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Twitter::SendOnTwitterService < Base::SendOnChannelService
  pattr_initialize [:message!]

  private

  delegate :additional_attributes, to: :contact

  def channel_class
    Channel::TwitterProfile
  end

  def perform_reply
    conversation_type == 'tweet' ? send_tweet_reply : send_direct_message
  end

  def twitter_client
    Twitty::Facade.new do |config|
      config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil)
      config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
      config.access_token = channel.twitter_access_token
      config.access_token_secret = channel.twitter_access_token_secret
      config.base_url = 'https://api.twitter.com'
      config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '')
    end
  end

  def conversation_type
    conversation.additional_attributes['type']
  end

  def screen_name
    return "@#{reply_to_message.inbox.name}" if reply_to_message.outgoing?

    "@#{reply_to_message.sender&.additional_attributes.try(:[], 'screen_name') || ''}"
  end

  def send_direct_message
    twitter_client.send_direct_message(
      recipient_id: contact_inbox.source_id,
      message: message.content
    )
  end

  def reply_to_message
    @reply_to_message ||= if message.in_reply_to
                            conversation.messages.find(message.in_reply_to)
                          else
                            conversation.messages.incoming.last
                          end
  end

  def send_tweet_reply
    response = twitter_client.send_tweet_reply(
      reply_to_tweet_id: reply_to_message.source_id,
      tweet: "#{screen_name} #{message.content}"
    )
    if response.status == '200'
      tweet_data = response.body
      message.update!(source_id: tweet_data['id_str'])
    else
      Rails.logger.error "TWITTER_TWEET_REPLY_ERROR #{response.body}"
    end
  end
end

```

Response:
require 'rails_helper'

describe Twitter::SendOnTwitterService do
  subject(:send_reply_service) { described_class.new(message: message) }

  let(:twitter_client) { instance_double(Twitty::Facade) }
  let(:twitter_response) { instance_double(Twitty::Response) }
  let(:account) { create(:account) }
  let(:widget_inbox) { create(:inbox, account: account) }
  let(:twitter_channel) { create(:channel_twitter_profile, account: account) }
  let(:twitter_inbox) { create(:inbox, channel: twitter_channel, account: account) }
  let(:contact) { create(:contact, account: account, additional_attributes: { screen_name: 'test_user' }) }
  let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: twitter_inbox) }
  let(:dm_conversation) do
    create(
      :conversation,
      contact: contact,
      inbox: twitter_inbox,
      contact_inbox: contact_inbox,
      additional_attributes: { type: 'direct_message' }
    )
  end
  let(:tweet_conversation) do
    create(
      :conversation,
      contact: contact,
      inbox: twitter_inbox,
      contact_inbox: contact_inbox,
      additional_attributes: { type: 'tweet', tweet_id: '1234' }
    )
  end

  before do
    allow(Twitty::Facade).to receive(:new).and_return(twitter_client)
    allow(twitter_client).to receive(:send_direct_message).and_return(true)
    allow(twitter_client).to receive(:send_tweet_reply).and_return(twitter_response)
    allow(twitter_response).to receive(:status).and_return('200')
    allow(twitter_response).to receive(:body).and_return(JSON.parse({ id_str: '12345' }.to_json))
  end

  describe '#perform' do
    context 'without reply' do
      it 'if inbox channel is not twitter profile' do
        message = create(:message, message_type: 'outgoing', inbox: widget_inbox, account: account)
        expect { described_class.new(message: message).perform }.to raise_error 'Invalid channel service was called'
        expect(twitter_client).not_to have_received(:send_direct_message)
      end

      it 'if message is private' do
        message = create(:message, message_type: 'outgoing', private: true, inbox: twitter_inbox, account: account)
        described_class.new(message: message).perform
        expect(twitter_client).not_to have_received(:send_direct_message)
      end

      it 'if message has source_id' do
        message = create(:message, message_type: 'outgoing', source_id: '123', inbox: twitter_inbox, account: account)
        described_class.new(message: message).perform
        expect(twitter_client).not_to have_received(:send_direct_message)
      end

      it 'if message is not outgoing' do
        message = create(:message, message_type: 'incoming', inbox: twitter_inbox, account: account)
        described_class.new(message: message).perform
        expect(twitter_client).not_to have_received(:send_direct_message)
      end
    end

    context 'with reply' do
      it 'if conversation is a direct message' do
        create(:message, message_type: :incoming, inbox: twitter_inbox, account: account, conversation: dm_conversation)
        message = create(:message, message_type: :outgoing, inbox: twitter_inbox, account: account, conversation: dm_conversation)
        described_class.new(message: message).perform
        expect(twitter_client).to have_received(:send_direct_message)
      end

      context 'when conversation is a tweet' do
        it 'creates a response with correct reply if reply to message is incoming' do
          create(
            :message,
            message_type: :incoming,
            sender: contact,
            source_id: 'test-source-id-1',
            inbox: twitter_inbox,
            account: account,
            conversation: tweet_conversation
          )
          message = create(:message, message_type: :outgoing, inbox: twitter_inbox, account: account, conversation: tweet_conversation)
          described_class.new(message: message).perform
          expect(twitter_client).to have_received(:send_tweet_reply).with(
            reply_to_tweet_id: 'test-source-id-1',
            tweet: "@test_user #{message.content}"
          )
          expect(message.reload.source_id).to eq '12345'
        end

        it 'creates a response with correct reply if reply to message is outgoing' do
          outgoing_message = create(
            :message,
            message_type: :outgoing,
            source_id: 'test-source-id-1',
            inbox: twitter_inbox,
            account: account,
            conversation: tweet_conversation
          )
          reply_message = create(
            :message,
            message_type: :outgoing,
            inbox: twitter_inbox,
            account: account,
            conversation: tweet_conversation,
            in_reply_to: outgoing_message.id
          )
          described_class.new(message: reply_message).perform
          expect(twitter_client).to have_received(:send_tweet_reply).with(
            reply_to_tweet_id: 'test-source-id-1',
            tweet: "@#{twitter_inbox.name} #{reply_message.content}"
          )
          expect(reply_message.reload.source_id).to eq '12345'
        end
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class AutoAssignment::InboxRoundRobinService
  pattr_initialize [:inbox!]

  # called on inbox delete
  def clear_queue
    ::Redis::Alfred.delete(round_robin_key)
  end

  # called on inbox member create
  def add_agent_to_queue(user_id)
    ::Redis::Alfred.lpush(round_robin_key, user_id)
  end

  # called on inbox member delete
  def remove_agent_from_queue(user_id)
    ::Redis::Alfred.lrem(round_robin_key, user_id)
  end

  def reset_queue
    clear_queue
    add_agent_to_queue(inbox.inbox_members.map(&:user_id))
  end

  # end of queue management functions

  # allowed member ids = [assignable online agents supplied by the assignement service]
  # the values of allowed member ids should be in string format
  def available_agent(allowed_agent_ids: [])
    reset_queue unless validate_queue?
    user_id = get_member_from_allowed_agent_ids(allowed_agent_ids)
    inbox.inbox_members.find_by(user_id: user_id)&.user if user_id.present?
  end

  private

  def get_member_from_allowed_agent_ids(allowed_agent_ids)
    return nil if allowed_agent_ids.blank?

    user_id = queue.intersection(allowed_agent_ids).pop
    pop_push_to_queue(user_id)
    user_id
  end

  def pop_push_to_queue(user_id)
    return if user_id.blank?

    remove_agent_from_queue(user_id)
    add_agent_to_queue(user_id)
  end

  def validate_queue?
    return true if inbox.inbox_members.map(&:user_id).sort == queue.map(&:to_i).sort
  end

  def queue
    ::Redis::Alfred.lrange(round_robin_key)
  end

  def round_robin_key
    format(::Redis::Alfred::ROUND_ROBIN_AGENTS, inbox_id: inbox.id)
  end
end

```

Response:
require 'rails_helper'

describe AutoAssignment::InboxRoundRobinService do
  subject(:inbox_round_robin_service) { described_class.new(inbox: inbox) }

  let!(:account) { create(:account) }
  let!(:inbox) { create(:inbox, account: account) }
  let!(:inbox_members) { create_list(:inbox_member, 5, inbox: inbox) }

  describe '#available_agent' do
    it 'returns nil if allowed_agent_ids is not passed or empty' do
      expect(described_class.new(inbox: inbox).available_agent).to be_nil
    end

    it 'gets the first available agent in allowed_agent_ids and move agent to end of the list' do
      expected_queue = [inbox_members[0].user_id, inbox_members[4].user_id, inbox_members[3].user_id, inbox_members[2].user_id,
                        inbox_members[1].user_id].map(&:to_s)
      described_class.new(inbox: inbox).available_agent(allowed_agent_ids: [inbox_members[0].user_id, inbox_members[4].user_id].map(&:to_s))
      expect(inbox_round_robin_service.send(:queue)).to eq(expected_queue)
    end

    it 'constructs round_robin_queue if queue is not present' do
      inbox_round_robin_service.clear_queue
      expect(inbox_round_robin_service.send(:queue)).to eq([])
      inbox_round_robin_service.available_agent
      # the service constructed the redis queue before performing
      expect(inbox_round_robin_service.send(:queue).map(&:to_i)).to match_array(inbox_members.map(&:user_id))
    end

    it 'validates the queue and correct it before performing round robin' do
      # adding some invalid ids to queue
      inbox_round_robin_service.add_agent_to_queue([2, 3, 5, 9])
      expect(inbox_round_robin_service.send(:queue).map(&:to_i)).not_to match_array(inbox_members.map(&:user_id))
      inbox_round_robin_service.available_agent
      # the service have refreshed the redis queue before performing
      expect(inbox_round_robin_service.send(:queue).map(&:to_i)).to match_array(inbox_members.map(&:user_id))
    end

    context 'when allowed_agent_ids is passed' do
      it 'will get the first allowed member and move it to the end of the queue' do
        expected_queue = [inbox_members[3].user_id, inbox_members[2].user_id, inbox_members[4].user_id, inbox_members[1].user_id,
                          inbox_members[0].user_id].map(&:to_s)
        expect(described_class.new(inbox: inbox).available_agent(
                 allowed_agent_ids: [
                   inbox_members[3].user_id,
                   inbox_members[2].user_id
                 ].map(&:to_s)
               )).to eq inbox_members[2].user
        expect(described_class.new(inbox: inbox).available_agent(
                 allowed_agent_ids: [
                   inbox_members[3].user_id,
                   inbox_members[2].user_id
                 ].map(&:to_s)
               )).to eq inbox_members[3].user
        expect(inbox_round_robin_service.send(:queue)).to eq(expected_queue)
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class AutoAssignment::AgentAssignmentService
  # Allowed agent ids: array
  # This is the list of agents from which an agent can be assigned to this conversation
  # examples: Agents with assignment capacity, Agents who are members of a team etc
  pattr_initialize [:conversation!, :allowed_agent_ids!]

  def find_assignee
    round_robin_manage_service.available_agent(allowed_agent_ids: allowed_online_agent_ids)
  end

  def perform
    new_assignee = find_assignee
    conversation.update(assignee: new_assignee) if new_assignee
  end

  private

  def online_agent_ids
    online_agents = OnlineStatusTracker.get_available_users(conversation.account_id)
    online_agents.select { |_key, value| value.eql?('online') }.keys if online_agents.present?
  end

  def allowed_online_agent_ids
    # We want to perform roundrobin only over online agents
    # Hence taking an intersection of online agents and allowed member ids

    # the online user ids are string, since its from redis, allowed member ids are integer, since its from active record
    @allowed_online_agent_ids ||= online_agent_ids & allowed_agent_ids&.map(&:to_s)
  end

  def round_robin_manage_service
    @round_robin_manage_service ||= AutoAssignment::InboxRoundRobinService.new(inbox: conversation.inbox)
  end

  def round_robin_key
    format(::Redis::Alfred::ROUND_ROBIN_AGENTS, inbox_id: conversation.inbox_id)
  end
end

```

Response:
require 'rails_helper'

RSpec.describe AutoAssignment::AgentAssignmentService do
  let!(:account) { create(:account) }
  let!(:inbox) { create(:inbox, account: account, enable_auto_assignment: false) }
  let!(:inbox_members) { create_list(:inbox_member, 5, inbox: inbox) }
  let!(:conversation) { create(:conversation, inbox: inbox, account: account) }
  let!(:online_users) do
    {
      inbox_members[0].user_id.to_s => 'busy',
      inbox_members[1].user_id.to_s => 'busy',
      inbox_members[2].user_id.to_s => 'busy',
      inbox_members[3].user_id.to_s => 'online',
      inbox_members[4].user_id.to_s => 'online'
    }
  end

  before do
    inbox_members.each { |inbox_member| create(:account_user, account: account, user: inbox_member.user) }
    allow(OnlineStatusTracker).to receive(:get_available_users).and_return(online_users)
  end

  describe '#perform' do
    it 'will assign an online agent to the conversation' do
      expect(conversation.reload.assignee).to be_nil
      described_class.new(conversation: conversation, allowed_agent_ids: inbox_members.map(&:user_id).map(&:to_s)).perform
      expect(conversation.reload.assignee).not_to be_nil
    end
  end

  describe '#find_assignee' do
    it 'will return an online agent from the allowed agent ids in roud robin' do
      expect(described_class.new(conversation: conversation,
                                 allowed_agent_ids: inbox_members.map(&:user_id).map(&:to_s)).find_assignee).to eq(inbox_members[3].user)
      expect(described_class.new(conversation: conversation,
                                 allowed_agent_ids: inbox_members.map(&:user_id).map(&:to_s)).find_assignee).to eq(inbox_members[4].user)
    end
  end
end


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

  def perform
    return unless valid_mention_message?(message)

    validated_mentioned_ids = filter_mentioned_ids_by_inbox
    return if validated_mentioned_ids.blank?

    Conversations::UserMentionJob.perform_later(validated_mentioned_ids, message.conversation.id, message.account.id)
    generate_notifications_for_mentions(validated_mentioned_ids)
    add_mentioned_users_as_participants(validated_mentioned_ids)
  end

  private

  def valid_mention_message?(message)
    message.private? && message.content.present? && mentioned_ids.present?
  end

  def mentioned_ids
    @mentioned_ids ||= message.content.scan(%r{\(mention://(user|team)/(\d+)/(.+?)\)}).map(&:second).uniq
  end

  def filter_mentioned_ids_by_inbox
    inbox = message.inbox
    valid_mentionable_ids = inbox.account.administrators.map(&:id) + inbox.members.map(&:id)
    # Intersection of ids
    mentioned_ids & valid_mentionable_ids.uniq.map(&:to_s)
  end

  def generate_notifications_for_mentions(validated_mentioned_ids)
    validated_mentioned_ids.each do |user_id|
      NotificationBuilder.new(
        notification_type: 'conversation_mention',
        user: User.find(user_id),
        account: message.account,
        primary_actor: message
      ).perform
    end
  end

  def add_mentioned_users_as_participants(validated_mentioned_ids)
    validated_mentioned_ids.each do |user_id|
      message.conversation.conversation_participants.find_or_create_by(user_id: user_id)
    end
  end
end

```

Response:
require 'rails_helper'

describe Messages::MentionService do
  let!(:account) { create(:account) }
  let!(:user) { create(:user, account: account) }
  let!(:first_agent) { create(:user, account: account) }
  let!(:second_agent) { create(:user, account: account) }
  let!(:inbox) { create(:inbox, account: account) }
  let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user) }
  let(:builder) { double }

  before do
    create(:inbox_member, user: first_agent, inbox: inbox)
    create(:inbox_member, user: second_agent, inbox: inbox)
    conversation.reload
    allow(NotificationBuilder).to receive(:new).and_return(builder)
    allow(builder).to receive(:perform)
  end

  context 'when message contains mention' do
    it 'creates notifications for inbox member who was mentioned' do
      message = build(
        :message,
        conversation: conversation,
        account: account,
        content: "hi [#{first_agent.name}](mention://user/#{first_agent.id}/#{first_agent.name})",
        private: true
      )

      described_class.new(message: message).perform

      expect(NotificationBuilder).to have_received(:new).with(notification_type: 'conversation_mention',
                                                              user: first_agent,
                                                              account: account,
                                                              primary_actor: message)
    end
  end

  context 'when message contains multiple mentions' do
    let(:message) do
      build(
        :message,
        conversation: conversation,
        account: account,
        content: "hey [#{second_agent.name}](mention://user/#{second_agent.id}/#{second_agent.name})/
                  [#{first_agent.name}](mention://user/#{first_agent.id}/#{first_agent.name}),
                   please look in to this?",
        private: true
      )
    end

    it 'creates notifications for inbox member who was mentioned' do
      described_class.new(message: message).perform

      expect(NotificationBuilder).to have_received(:new).with(notification_type: 'conversation_mention',
                                                              user: second_agent,
                                                              account: account,
                                                              primary_actor: message)
      expect(NotificationBuilder).to have_received(:new).with(notification_type: 'conversation_mention',
                                                              user: first_agent,
                                                              account: account,
                                                              primary_actor: message)
    end

    it 'add the users to the participants list' do
      described_class.new(message: message).perform
      expect(conversation.conversation_participants.map(&:user_id)).to contain_exactly(first_agent.id, second_agent.id)
    end
  end
end


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

  def perform
    return unless message.notifiable?

    notify_participating_users
    notify_conversation_assignee
  end

  private

  delegate :conversation, :sender, :account, to: :message

  def notify_participating_users
    participating_users = conversation.conversation_participants.map(&:user)
    participating_users -= [sender] if sender.is_a?(User)

    participating_users.uniq.each do |participant|
      NotificationBuilder.new(
        notification_type: 'participating_conversation_new_message',
        user: participant,
        account: account,
        primary_actor: message
      ).perform
    end
  end

  def notify_conversation_assignee
    return if conversation.assignee.blank?
    return if assignee_already_notified_via_participation?
    return if conversation.assignee == sender

    NotificationBuilder.new(
      notification_type: 'assigned_conversation_new_message',
      user: conversation.assignee,
      account: account,
      primary_actor: message
    ).perform
  end

  def assignee_already_notified_via_participation?
    return unless conversation.conversation_participants.map(&:user).include?(conversation.assignee)

    # check whether participation notifcation is disabled for assignee
    notification_setting = conversation.assignee.notification_settings.find_by(account_id: account.id)
    notification_setting.public_send(:email_participating_conversation_new_message?) || notification_setting
      .public_send(:push_participating_conversation_new_message?)
  end
end

```

Response:
require 'rails_helper'

describe Messages::NewMessageNotificationService do
  context 'when message is not notifiable' do
    it 'will not create any notifications' do
      message = build(:message, message_type: :activity)
      expect(NotificationBuilder).not_to receive(:new)
      described_class.new(message: message).perform
    end
  end

  context 'when message is notifiable' do
    let(:account) { create(:account) }
    let(:assignee) { create(:user, account: account) }
    let(:participating_agent_1) { create(:user, account: account) }
    let(:participating_agent_2) { create(:user, account: account) }
    let(:inbox) { create(:inbox, account: account) }
    let(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: assignee) }
    let(:builder) { double }

    before do
      create(:inbox_member, inbox: inbox, user: participating_agent_1)
      create(:inbox_member, inbox: inbox, user: participating_agent_2)
      create(:inbox_member, inbox: inbox, user: assignee)
      create(:conversation_participant, conversation: conversation, user: participating_agent_1)
      create(:conversation_participant, conversation: conversation, user: participating_agent_2)
      create(:conversation_participant, conversation: conversation, user: assignee)
      allow(NotificationBuilder).to receive(:new).and_return(builder)
      allow(builder).to receive(:perform)
    end

    context 'when message is created by a participant' do
      let(:message) { create(:message, conversation: conversation, account: account, sender: participating_agent_1) }

      before do
        described_class.new(message: message).perform
      end

      it 'creates notifications for other participating users' do
        expect(NotificationBuilder).to have_received(:new).with(notification_type: 'participating_conversation_new_message',
                                                                user: participating_agent_2,
                                                                account: account,
                                                                primary_actor: message)
      end

      it 'creates notifications for assignee' do
        expect(NotificationBuilder).to have_received(:new).with(notification_type: 'assigned_conversation_new_message',
                                                                user: assignee,
                                                                account: account,
                                                                primary_actor: message)
      end

      it 'will not create notifications for the user who created the message' do
        expect(NotificationBuilder).not_to have_received(:new).with(notification_type: 'participating_conversation_new_message',
                                                                    user: participating_agent_1,
                                                                    account: account,
                                                                    primary_actor: message)
      end
    end

    context 'when message is created by a contact' do
      let(:message) { create(:message, conversation: conversation, account: account) }

      before do
        described_class.new(message: message).perform
      end

      it 'creates notifications for assignee' do
        expect(NotificationBuilder).to have_received(:new).with(notification_type: 'assigned_conversation_new_message',
                                                                user: assignee,
                                                                account: account,
                                                                primary_actor: message)
      end

      it 'creates notifications for all participating users' do
        expect(NotificationBuilder).to have_received(:new).with(notification_type: 'participating_conversation_new_message',
                                                                user: participating_agent_1,
                                                                account: account,
                                                                primary_actor: message)
        expect(NotificationBuilder).to have_received(:new).with(notification_type: 'participating_conversation_new_message',
                                                                user: participating_agent_2,
                                                                account: account,
                                                                primary_actor: message)
      end
    end

    context 'with multiple notifications are subscribed' do
      let(:message) { create(:message, conversation: conversation, account: account) }

      before do
        assignee.notification_settings.find_by(account_id: account.id).update(selected_email_flags: %w[email_assigned_conversation_new_message
                                                                                email_participating_conversation_new_message])
        described_class.new(message: message).perform
      end

      it 'will not create assignee notifications for the assignee if participating notification was send' do
        expect(NotificationBuilder).not_to have_received(:new).with(notification_type: 'assigned_conversation_new_message',
                                                                    user: assignee,
                                                                    account: account,
                                                                    primary_actor: message)
      end
    end

    context 'when message is created by assignee' do
      let(:message) { create(:message, conversation: conversation, account: account, sender: assignee) }

      before do
        described_class.new(message: message).perform
      end

      it 'will not create notifications for the user who created the message' do
        expect(NotificationBuilder).not_to have_received(:new).with(notification_type: 'participating_conversation_new_message',
                                                                    user: assignee,
                                                                    account: account,
                                                                    primary_actor: message)
        expect(NotificationBuilder).not_to have_received(:new).with(notification_type: 'assigned_conversation_new_message',
                                                                    user: assignee,
                                                                    account: account,
                                                                    primary_actor: message)
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Sms::DeliveryStatusService
  pattr_initialize [:inbox!, :params!]

  def perform
    return unless supported_status?

    process_status if message.present?
  end

  private

  def process_status
    @message.status = status
    @message.external_error = external_error if error_occurred?
    @message.save!
  end

  def supported_status?
    %w[message-delivered message-failed].include?(params[:type])
  end

  # Relevant documentation:
  # https://dev.bandwidth.com/docs/mfa/webhooks/international/message-delivered
  # https://dev.bandwidth.com/docs/mfa/webhooks/international/message-failed
  def status
    type_mapping = {
      'message-delivered' => 'delivered',
      'message-failed' => 'failed'
    }

    type_mapping[params[:type]]
  end

  def external_error
    return nil unless error_occurred?

    error_message = params[:description]
    error_code = params[:errorCode]

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

  def error_occurred?
    params[:errorCode] && params[:type] == 'message-failed'
  end

  def message
    return unless params[:message][:id]

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

```

Response:
require 'rails_helper'

describe Sms::DeliveryStatusService do
  describe '#perform' do
    let!(:account) { create(:account) }
    let!(:sms_channel) { create(:channel_sms) }
    let!(:contact) { create(:contact, account: account, phone_number: '+12345') }
    let(:contact_inbox) { create(:contact_inbox, source_id: '+12345', contact: contact, inbox: sms_channel.inbox) }
    let!(:conversation) { create(:conversation, contact: contact, inbox: sms_channel.inbox, contact_inbox: contact_inbox) }

    describe '#perform' do
      context 'when message delivery status is fired' do
        before do
          create(:message, account: account, inbox: sms_channel.inbox, conversation: conversation, status: :sent,
                           source_id: 'SMd560ac79e4a4d36b3ce59f1f50471986')
        end

        it 'updates the message if the message status is delivered' do
          params = {
            time: '2022-02-02T23:14:05.309Z',
            type: 'message-delivered',
            to: sms_channel.phone_number,
            description: 'ok',
            message: {
              'id': conversation.messages.last.source_id
            }
          }

          described_class.new(params: params, inbox: sms_channel.inbox).perform
          expect(conversation.reload.messages.last.status).to eq('delivered')
        end

        it 'updates the message if the message status is failed' do
          params = {
            time: '2022-02-02T23:14:05.309Z',
            type: 'message-failed',
            to: sms_channel.phone_number,
            description: 'Undeliverable',
            errorCode: 995,
            message: {
              'id': conversation.messages.last.source_id
            }
          }

          described_class.new(params: params, inbox: sms_channel.inbox).perform
          expect(conversation.reload.messages.last.status).to eq('failed')

          expect(conversation.reload.messages.last.external_error).to eq('995 - Undeliverable')
        end

        it 'does not update the message if the status is not a support status' do
          params = {
            time: '2022-02-02T23:14:05.309Z',
            type: 'queued',
            to: sms_channel.phone_number,
            description: 'ok',
            message: {
              'id': conversation.messages.last.source_id
            }
          }

          described_class.new(params: params, inbox: sms_channel.inbox).perform
          expect(conversation.reload.messages.last.status).to eq('sent')
        end

        it 'does not update the message if the message is not present' do
          params = {
            time: '2022-02-02T23:14:05.309Z',
            type: 'message-delivered',
            to: sms_channel.phone_number,
            description: 'ok',
            message: {
              'id': '123'
            }
          }

          described_class.new(params: params, inbox: sms_channel.inbox).perform
          expect(conversation.reload.messages.last.status).to eq('sent')
        end
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Sms::IncomingMessageService
  include ::FileTypeHelper

  pattr_initialize [:inbox!, :params!]

  def perform
    set_contact
    set_conversation
    @message = @conversation.messages.create!(
      content: params[:text],
      account_id: @inbox.account_id,
      inbox_id: @inbox.id,
      message_type: :incoming,
      sender: @contact,
      source_id: params[:id]
    )
    attach_files
    @message.save!
  end

  private

  def account
    @account ||= @inbox.account
  end

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

  def phone_number
    params[:from]
  end

  def formatted_phone_number
    TelephoneNumber.parse(phone_number).international_number
  end

  def set_contact
    contact_inbox = ::ContactInboxWithContactBuilder.new(
      source_id: params[:from],
      inbox: @inbox,
      contact_attributes: contact_attributes
    ).perform

    @contact_inbox = contact_inbox
    @contact = contact_inbox.contact
  end

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

  def set_conversation
    # if lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved
    @conversation = if @inbox.lock_to_single_conversation
                      @contact_inbox.conversations.last
                    else
                      @contact_inbox.conversations.where
                                    .not(status: :resolved).last
                    end
    return if @conversation

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

  def contact_attributes
    {
      name: formatted_phone_number,
      phone_number: phone_number
    }
  end

  def attach_files
    return if params[:media].blank?

    params[:media].each do |media_url|
      # we don't need to process this files since chatwoot doesn't support it
      next if media_url.end_with?('.smil', '.xml')

      attachment_file = Down.download(
        media_url,
        http_basic_authentication: [channel.provider_config['api_key'], channel.provider_config['api_secret']]
      )

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

```

Response:
require 'rails_helper'

describe Sms::IncomingMessageService do
  describe '#perform' do
    let!(:sms_channel) { create(:channel_sms) }
    let(:params) do
      {

        'id': '3232420-2323-234324',
        'owner': sms_channel.phone_number,
        'applicationId': '2342349-324234d-32432432',
        'time': '2022-02-02T23:14:05.262Z',
        'segmentCount': 1,
        'direction': 'in',
        'to': [
          sms_channel.phone_number
        ],
        'from': '+14234234234',
        'text': 'test message'

      }.with_indifferent_access
    end

    context 'when valid text message params' do
      it 'creates appropriate conversations, message and contacts' do
        described_class.new(inbox: sms_channel.inbox, params: params).perform
        expect(sms_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('+1 423-423-4234')
        expect(sms_channel.inbox.messages.first.content).to eq(params[:text])
      end

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

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

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

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

      it 'creates attachment messages and ignores .smil files' do
        stub_request(:get, 'http://test.com/test.png').to_return(status: 200, body: File.read('spec/assets/sample.png'), headers: {})
        stub_request(:get, 'http://test.com/test2.png').to_return(status: 200, body: File.read('spec/assets/sample.png'), headers: {})

        media_params = { 'media': [
          'http://test.com/test.smil',
          'http://test.com/test.png',
          'http://test.com/test2.png'
        ] }.with_indifferent_access

        described_class.new(inbox: sms_channel.inbox, params: params.merge(media_params)).perform
        expect(sms_channel.inbox.conversations.count).not_to eq(0)
        expect(Contact.all.first.name).to eq('+1 423-423-4234')
        expect(sms_channel.inbox.messages.first.content).to eq('test message')
        expect(sms_channel.inbox.messages.first.attachments.present?).to be true
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Sms::SendOnSmsService < Base::SendOnChannelService
  private

  def channel_class
    Channel::Sms
  end

  def perform_reply
    send_on_sms
  end

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

```

Response:
require 'rails_helper'

describe Sms::SendOnSmsService do
  describe '#perform' do
    context 'when a valid message' do
      let(:sms_request) { double }
      let!(:sms_channel) { create(:channel_sms) }
      let!(:contact_inbox) { create(:contact_inbox, inbox: sms_channel.inbox, source_id: '+123456789') }
      let!(:conversation) { create(:conversation, contact_inbox: contact_inbox, inbox: sms_channel.inbox) }

      it 'calls channel.send_message' do
        message = create(:message, message_type: :outgoing, content: 'test',
                                   conversation: conversation)
        allow(HTTParty).to receive(:post).and_return(sms_request)
        allow(sms_request).to receive(:success?).and_return(true)
        allow(sms_request).to receive(:parsed_response).and_return({ 'id' => '123456789' })
        expect(HTTParty).to receive(:post).with(
          'https://messaging.bandwidth.com/api/v2/users/1/messages',
          basic_auth: { username: '1', password: '1' },
          headers: { 'Content-Type' => 'application/json' },
          body: { 'to' => '+123456789', 'from' => sms_channel.phone_number, 'text' => 'test', 'applicationId' => '1' }.to_json
        )
        described_class.new(message: message).perform
        expect(message.reload.source_id).to eq('123456789')
      end

      it 'calls channel.send_message with attachments' do
        message = build(:message, message_type: :outgoing, content: 'test',
                                  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')
        attachment2 = message.attachments.new(account_id: message.account_id, file_type: :image)
        attachment2.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
        message.save!

        allow(HTTParty).to receive(:post).and_return(sms_request)
        allow(sms_request).to receive(:success?).and_return(true)
        allow(sms_request).to receive(:parsed_response).and_return({ 'id' => '123456789' })
        allow(attachment).to receive(:download_url).and_return('url1')
        allow(attachment2).to receive(:download_url).and_return('url2')
        expect(HTTParty).to receive(:post).with(
          'https://messaging.bandwidth.com/api/v2/users/1/messages',
          basic_auth: { username: '1', password: '1' },
          headers: { 'Content-Type' => 'application/json' },
          body: { 'to' => '+123456789', 'from' => sms_channel.phone_number, 'text' => 'test', 'applicationId' => '1',
                  'media' => %w[url1 url2] }.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
class Sms::OneoffSmsCampaignService
  pattr_initialize [:campaign!]

  def perform
    raise "Invalid campaign #{campaign.id}" if campaign.inbox.inbox_type != 'Sms' || !campaign.one_off?
    raise 'Completed Campaign' if campaign.completed?

    # marks campaign completed so that other jobs won't pick it up
    campaign.completed!

    audience_label_ids = campaign.audience.select { |audience| audience['type'] == 'Label' }.pluck('id')
    audience_labels = campaign.account.labels.where(id: audience_label_ids).pluck(:title)
    process_audience(audience_labels)
  end

  private

  delegate :inbox, to: :campaign
  delegate :channel, to: :inbox

  def process_audience(audience_labels)
    campaign.account.contacts.tagged_with(audience_labels, any: true).each do |contact|
      next if contact.phone_number.blank?

      send_message(to: contact.phone_number, content: campaign.message)
    end
  end

  def send_message(to:, content:)
    channel.send_text_message(to, content)
  end
end

```

Response:
require 'rails_helper'

describe Sms::OneoffSmsCampaignService do
  subject(:sms_campaign_service) { described_class.new(campaign: campaign) }

  let(:account) { create(:account) }
  let!(:sms_channel) { create(:channel_sms) }
  let!(:sms_inbox) { create(:inbox, channel: sms_channel) }
  let(:label1) { create(:label, account: account) }
  let(:label2) { create(:label, account: account) }
  let!(:campaign) do
    create(:campaign, inbox: sms_inbox, account: account,
                      audience: [{ type: 'Label', id: label1.id }, { type: 'Label', id: label2.id }])
  end

  describe 'perform' do
    before do
      stub_request(:post, 'https://messaging.bandwidth.com/api/v2/users/1/messages').to_return(
        status: 200,
        body: { 'id' => '1' }.to_json,
        headers: {}
      )
    end

    it 'raises error if the campaign is completed' do
      campaign.completed!

      expect { sms_campaign_service.perform }.to raise_error 'Completed Campaign'
    end

    it 'raises error invalid campaign when its not a oneoff sms campaign' do
      campaign = create(:campaign)

      expect { described_class.new(campaign: campaign).perform }.to raise_error "Invalid campaign #{campaign.id}"
    end

    it 'send messages to contacts in the audience and marks the campaign completed' do
      contact_with_label1, contact_with_label2, contact_with_both_labels = FactoryBot.create_list(:contact, 3, :with_phone_number, account: account)
      contact_with_label1.update_labels([label1.title])
      contact_with_label2.update_labels([label2.title])
      contact_with_both_labels.update_labels([label1.title, label2.title])
      sms_campaign_service.perform
      assert_requested(:post, 'https://messaging.bandwidth.com/api/v2/users/1/messages', times: 3)
      expect(campaign.reload.completed?).to be true
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Notification::PushNotificationService
  include Rails.application.routes.url_helpers

  pattr_initialize [:notification!]

  def perform
    return unless user_subscribed_to_notification?

    notification_subscriptions.each do |subscription|
      send_browser_push(subscription)
      send_fcm_push(subscription)
      send_push_via_chatwoot_hub(subscription)
    end
  end

  private

  delegate :user, to: :notification
  delegate :notification_subscriptions, to: :user
  delegate :notification_settings, to: :user

  def user_subscribed_to_notification?
    notification_setting = notification_settings.find_by(account_id: notification.account.id)
    return true if notification_setting.public_send("push_#{notification.notification_type}?")

    false
  end

  def conversation
    @conversation ||= notification.conversation
  end

  def push_message
    {
      title: notification.push_message_title,
      tag: "#{notification.notification_type}_#{conversation.display_id}_#{notification.id}",
      url: push_url
    }
  end

  def push_url
    app_account_conversation_url(account_id: conversation.account_id, id: conversation.display_id)
  end

  def send_browser_push?(subscription)
    VapidService.public_key && subscription.browser_push?
  end

  def send_browser_push(subscription)
    return unless send_browser_push?(subscription)

    WebPush.payload_send(
      message: JSON.generate(push_message),
      endpoint: subscription.subscription_attributes['endpoint'],
      p256dh: subscription.subscription_attributes['p256dh'],
      auth: subscription.subscription_attributes['auth'],
      vapid: {
        subject: push_url,
        public_key: VapidService.public_key,
        private_key: VapidService.private_key
      },
      ssl_timeout: 5,
      open_timeout: 5,
      read_timeout: 5
    )
  rescue WebPush::ExpiredSubscription
    subscription.destroy!
  rescue Errno::ECONNRESET, Net::OpenTimeout, Net::ReadTimeout => e
    Rails.logger.error "WebPush operation error: #{e.message}"
  end

  def send_fcm_push(subscription)
    return unless ENV['FCM_SERVER_KEY']
    return unless subscription.fcm?

    fcm = FCM.new(ENV.fetch('FCM_SERVER_KEY', nil))
    response = fcm.send([subscription.subscription_attributes['push_token']], fcm_options)
    remove_subscription_if_error(subscription, response)
  end

  def send_push_via_chatwoot_hub(subscription)
    return if ENV['FCM_SERVER_KEY']
    return unless ActiveModel::Type::Boolean.new.cast(ENV.fetch('ENABLE_PUSH_RELAY_SERVER', true))
    return unless subscription.fcm?

    ChatwootHub.send_browser_push([subscription.subscription_attributes['push_token']], fcm_options)
  end

  def remove_subscription_if_error(subscription, response)
    subscription.destroy! if JSON.parse(response[:body])['results']&.first&.keys&.include?('error')
  end

  def fcm_options
    {
      notification: {
        title: notification.notification_type.titleize,
        body: notification.push_message_title,
        sound: 'default'
      },
      android: { priority: 'high' },
      data: { notification: notification.fcm_push_data.to_json },
      collapse_key: "chatwoot_#{notification.primary_actor_type.downcase}_#{notification.primary_actor_id}"
    }
  end
end

```

Response:
require 'rails_helper'

describe Notification::PushNotificationService do
  let!(:account) { create(:account) }
  let!(:user) { create(:user, account: account) }
  let!(:notification) { create(:notification, user: user, account: user.accounts.first) }
  let(:fcm_double) { double }

  before do
    allow(WebPush).to receive(:payload_send).and_return(true)
    allow(FCM).to receive(:new).and_return(fcm_double)
    allow(fcm_double).to receive(:send).and_return({ body: { 'results': [] }.to_json })
  end

  describe '#perform' do
    it 'sends webpush notifications for webpush subscription' do
      with_modified_env VAPID_PUBLIC_KEY: 'test' do
        create(:notification_subscription, user: notification.user)

        described_class.new(notification: notification).perform
        expect(WebPush).to have_received(:payload_send)
        expect(FCM).not_to have_received(:new)
      end
    end

    it 'sends a fcm notification for firebase subscription' do
      with_modified_env FCM_SERVER_KEY: 'test', ENABLE_PUSH_RELAY_SERVER: 'false' do
        create(:notification_subscription, user: notification.user, subscription_type: 'fcm')

        described_class.new(notification: notification).perform
        expect(FCM).to have_received(:new)
        expect(WebPush).not_to have_received(:payload_send)
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# refer: https://gitlab.com/gitlab-org/ruby/gems/gitlab-mail_room/-/blob/master/lib/mail_room/microsoft_graph/connection.rb
# refer: https://github.com/microsoftgraph/msgraph-sample-rubyrailsapp/tree/b4a6869fe4a438cde42b161196484a929f1bee46
# https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-configurable-token-lifetimes
class Microsoft::RefreshOauthTokenService
  pattr_initialize [:channel!]

  # if the token is not expired yet then skip the refresh token step
  def access_token
    provider_config = channel.provider_config.with_indifferent_access
    if Time.current.utc >= expires_on(provider_config['expires_on'])
      # Token expired, refresh
      new_hash = refresh_tokens
      new_hash[:access_token]
    else
      provider_config[:access_token]
    end
  end

  def expires_on(expiry)
    # we will give it a 5 minute gap for safety
    expiry.presence ? DateTime.parse(expiry) - 5.minutes : Time.current.utc
  end

  # <RefreshTokensSnippet>
  def refresh_tokens
    token_hash = channel.provider_config.with_indifferent_access
    oauth_strategy = ::MicrosoftGraphAuth.new(
      nil, ENV.fetch('AZURE_APP_ID', nil), ENV.fetch('AZURE_APP_SECRET', nil)
    )

    token_service = OAuth2::AccessToken.new(
      oauth_strategy.client, token_hash['access_token'],
      refresh_token: token_hash['refresh_token']
    )

    # Refresh the tokens
    new_tokens = token_service.refresh!.to_hash.slice(:access_token, :refresh_token, :expires_at)

    update_channel_provider_config(new_tokens)
    channel.provider_config
  end
  # </RefreshTokensSnippet>

  def update_channel_provider_config(new_tokens)
    new_tokens = new_tokens.with_indifferent_access
    channel.provider_config = {
      access_token: new_tokens[:access_token],
      refresh_token: new_tokens[:refresh_token],
      expires_on: Time.at(new_tokens[:expires_at]).utc.to_s
    }
    channel.save!
  end
end

```

Response:
require 'rails_helper'

RSpec.describe Microsoft::RefreshOauthTokenService do
  let(:access_token) { SecureRandom.hex }
  let(:refresh_token) { SecureRandom.hex }
  let(:expires_on) { Time.zone.now + 3600 }

  let!(:microsoft_email_channel) do
    create(:channel_email, provider_config: { access_token: access_token, refresh_token: refresh_token, expires_on: expires_on })
  end
  let(:new_tokens) { { access_token: access_token, refresh_token: refresh_token, expires_at: expires_on.to_i, token_type: 'bearer' } }

  describe '#access_token' do
    context 'when token is not expired' do
      it 'returns the existing access token' do
        expect(described_class.new(channel: microsoft_email_channel).access_token).to eq(access_token)
        expect(microsoft_email_channel.reload.provider_config['refresh_token']).to eq(refresh_token)
      end
    end

    context 'when token is expired' do
      let(:expires_on) { 1.minute.from_now }

      before do
        stub_request(:post, 'https://login.microsoftonline.com/common/oauth2/v2.0/token').with(
          body: { 'grant_type' => 'refresh_token', 'refresh_token' => refresh_token }
        ).to_return(status: 200, body: new_tokens.to_json, headers: { 'Content-Type' => 'application/json' })
      end

      it 'fetches new access token and refresh tokens' do
        microsoft_email_channel.provider_config['expires_on'] = Time.zone.now - 3600
        microsoft_email_channel.save!

        expect(described_class.new(channel: microsoft_email_channel).access_token).not_to eq(access_token)
        expect(microsoft_email_channel.reload.provider_config['access_token']).to eq(new_tokens[:access_token])
        expect(microsoft_email_channel.reload.provider_config['refresh_token']).to eq(new_tokens[:refresh_token])
        expect(microsoft_email_channel.reload.provider_config['expires_on']).to eq(Time.at(new_tokens[:expires_at]).utc.to_s)
      end
    end

    context 'when refresh token is not present in provider config and access token is expired' do
      it 'throws an error' do
        microsoft_email_channel.update(provider_config: {
                                         access_token: access_token,
                                         expires_on: expires_on - 3600
                                       })
        expect do
          described_class.new(channel: microsoft_email_channel).access_token
        end.to raise_error(RuntimeError, 'A refresh_token is not available')
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
class Contacts::ContactableInboxesService
  pattr_initialize [:contact!]

  def get
    account = contact.account
    account.inboxes.filter_map { |inbox| get_contactable_inbox(inbox) }
  end

  private

  def get_contactable_inbox(inbox)
    case inbox.channel_type
    when 'Channel::TwilioSms'
      twilio_contactable_inbox(inbox)
    when 'Channel::Whatsapp'
      whatsapp_contactable_inbox(inbox)
    when 'Channel::Sms'
      sms_contactable_inbox(inbox)
    when 'Channel::Email'
      email_contactable_inbox(inbox)
    when 'Channel::Api'
      api_contactable_inbox(inbox)
    when 'Channel::WebWidget'
      website_contactable_inbox(inbox)
    end
  end

  def website_contactable_inbox(inbox)
    latest_contact_inbox = inbox.contact_inboxes.where(contact: @contact).last
    return unless latest_contact_inbox
    # FIXME : change this when multiple conversations comes in
    return if latest_contact_inbox.conversations.present?

    { source_id: latest_contact_inbox.source_id, inbox: inbox }
  end

  def api_contactable_inbox(inbox)
    latest_contact_inbox = inbox.contact_inboxes.where(contact: @contact).last
    source_id = latest_contact_inbox&.source_id || SecureRandom.uuid

    { source_id: source_id, inbox: inbox }
  end

  def email_contactable_inbox(inbox)
    return unless @contact.email

    { source_id: @contact.email, inbox: inbox }
  end

  def whatsapp_contactable_inbox(inbox)
    return unless @contact.phone_number

    # Remove the plus since thats the format 360 dialog uses
    { source_id: @contact.phone_number.delete('+'), inbox: inbox }
  end

  def sms_contactable_inbox(inbox)
    return unless @contact.phone_number

    { source_id: @contact.phone_number, inbox: inbox }
  end

  def twilio_contactable_inbox(inbox)
    return if @contact.phone_number.blank?

    case inbox.channel.medium
    when 'sms'
      { source_id: @contact.phone_number, inbox: inbox }
    when 'whatsapp'
      { source_id: "whatsapp:#{@contact.phone_number}", inbox: inbox }
    end
  end
end

```

Response:
require 'rails_helper'

describe Contacts::ContactableInboxesService do
  before do
    stub_request(:post, /graph.facebook.com/)
  end

  let(:account) { create(:account) }
  let(:contact) { create(:contact, account: account, email: 'contact@example.com', phone_number: '+2320000') }
  let!(:twilio_sms) { create(:channel_twilio_sms, account: account) }
  let!(:twilio_sms_inbox) { create(:inbox, channel: twilio_sms, account: account) }
  let!(:twilio_whatsapp) { create(:channel_twilio_sms, medium: :whatsapp, account: account) }
  let!(:twilio_whatsapp_inbox) { create(:inbox, channel: twilio_whatsapp, account: account) }
  let!(:email_channel) { create(:channel_email, account: account) }
  let!(:email_inbox) { create(:inbox, channel: email_channel, account: account) }
  let!(:api_channel) { create(:channel_api, account: account) }
  let!(:api_inbox) { create(:inbox, channel: api_channel, account: account) }
  let!(:website_inbox) { create(:inbox, channel: create(:channel_widget, account: account), account: account) }
  let!(:sms_inbox) { create(:inbox, channel: create(:channel_sms, account: account), account: account) }

  describe '#get' do
    it 'returns the contactable inboxes for the contact' do
      contactable_inboxes = described_class.new(contact: contact).get

      expect(contactable_inboxes).to include({ source_id: contact.phone_number, inbox: twilio_sms_inbox })
      expect(contactable_inboxes).to include({ source_id: "whatsapp:#{contact.phone_number}", inbox: twilio_whatsapp_inbox })
      expect(contactable_inboxes).to include({ source_id: contact.email, inbox: email_inbox })
      expect(contactable_inboxes).to include({ source_id: contact.phone_number, inbox: sms_inbox })
    end

    it 'doest not return the non contactable inboxes for the contact' do
      facebook_channel = create(:channel_facebook_page, account: account)
      facebook_inbox = create(:inbox, channel: facebook_channel, account: account)
      twitter_channel = create(:channel_twitter_profile, account: account)
      twitter_inbox = create(:inbox, channel: twitter_channel, account: account)

      contactable_inboxes = described_class.new(contact: contact).get

      expect(contactable_inboxes.pluck(:inbox)).not_to include(website_inbox)
      expect(contactable_inboxes.pluck(:inbox)).not_to include(facebook_inbox)
      expect(contactable_inboxes.pluck(:inbox)).not_to include(twitter_inbox)
    end

    context 'when api inbox is available' do
      it 'returns existing source id if contact inbox exists' do
        contact_inbox = create(:contact_inbox, inbox: api_inbox, contact: contact)

        contactable_inboxes = described_class.new(contact: contact).get
        expect(contactable_inboxes).to include({ source_id: contact_inbox.source_id, inbox: api_inbox })
      end
    end

    context 'when website inbox is available' do
      it 'returns existing source id if contact inbox exists without any conversations' do
        contact_inbox = create(:contact_inbox, inbox: website_inbox, contact: contact)

        contactable_inboxes = described_class.new(contact: contact).get
        expect(contactable_inboxes).to include({ source_id: contact_inbox.source_id, inbox: website_inbox })
      end

      it 'does not return existing source id if contact inbox exists with conversations' do
        contact_inbox = create(:contact_inbox, inbox: website_inbox, contact: contact)
        create(:conversation, contact: contact, inbox: website_inbox, contact_inbox: contact_inbox)

        contactable_inboxes = described_class.new(contact: contact).get
        expect(contactable_inboxes.pluck(:inbox)).not_to include(website_inbox)
      end
    end
  end
end