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

require 'csv'

# NOTE: This is a deprecated service, only kept to not break ongoing imports
# on upgrade. See `BulkImportService` for its replacement.

class ImportService < BaseService
  ROWS_PROCESSING_LIMIT = 20_000

  def call(import)
    @import  = import
    @account = @import.account

    case @import.type
    when 'following'
      import_follows!
    when 'blocking'
      import_blocks!
    when 'muting'
      import_mutes!
    when 'domain_blocking'
      import_domain_blocks!
    when 'bookmarks'
      import_bookmarks!
    end
  end

  private

  def import_follows!
    parse_import_data!(['Account address'])
    import_relationships!('follow', 'unfollow', @account.following, ROWS_PROCESSING_LIMIT, reblogs: { header: 'Show boosts', default: true }, notify: { header: 'Notify on new posts', default: false }, languages: { header: 'Languages', default: nil })
  end

  def import_blocks!
    parse_import_data!(['Account address'])
    import_relationships!('block', 'unblock', @account.blocking, ROWS_PROCESSING_LIMIT)
  end

  def import_mutes!
    parse_import_data!(['Account address'])
    import_relationships!('mute', 'unmute', @account.muting, ROWS_PROCESSING_LIMIT, notifications: { header: 'Hide notifications', default: true })
  end

  def import_domain_blocks!
    parse_import_data!(['#domain'])
    items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#domain'].strip }

    if @import.overwrite?
      presence_hash = items.index_with(true)

      @account.domain_blocks.find_each do |domain_block|
        if presence_hash[domain_block.domain]
          items.delete(domain_block.domain)
        else
          @account.unblock_domain!(domain_block.domain)
        end
      end
    end

    items.each do |domain|
      @account.block_domain!(domain)
    end

    AfterAccountDomainBlockWorker.push_bulk(items) do |domain|
      [@account.id, domain]
    end
  end

  def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {})
    local_domain_suffix = "@#{Rails.configuration.x.local_domain}"
    items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), extra_fields.to_h { |key, field_settings| [key, row[field_settings[:header]]&.strip || field_settings[:default]] }] }.reject { |(id, _)| id.blank? }

    if @import.overwrite?
      presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] }

      overwrite_scope.reorder(nil).find_each do |target_account|
        if presence_hash[target_account.acct]
          items.delete(target_account.acct)
          extra = presence_hash[target_account.acct][1]
          Import::RelationshipWorker.perform_async(@account.id, target_account.acct, action, extra.stringify_keys)
        else
          Import::RelationshipWorker.perform_async(@account.id, target_account.acct, undo_action)
        end
      end
    end

    head_items = items.uniq { |acct, _| acct.split('@')[1] }
    tail_items = items - head_items

    Import::RelationshipWorker.push_bulk(head_items + tail_items) do |acct, extra|
      [@account.id, acct, action, extra.stringify_keys]
    end
  end

  def import_bookmarks!
    parse_import_data!(['#uri'])
    items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#uri'].strip }

    if @import.overwrite?
      presence_hash = items.index_with(true)

      @account.bookmarks.find_each do |bookmark|
        if presence_hash[bookmark.status.uri]
          items.delete(bookmark.status.uri)
        else
          bookmark.destroy!
        end
      end
    end

    statuses = items.filter_map do |uri|
      status = ActivityPub::TagManager.instance.uri_to_resource(uri, Status)
      next if status.nil? && ActivityPub::TagManager.instance.local_uri?(uri)

      status || ActivityPub::FetchRemoteStatusService.new.call(uri)
    rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError
      nil
    rescue => e
      Rails.logger.warn "Unexpected error when importing bookmark: #{e}"
      nil
    end

    account_ids         = statuses.map(&:account_id)
    preloaded_relations = @account.relations_map(account_ids, skip_blocking_and_muting: true)

    statuses.keep_if { |status| StatusPolicy.new(@account, status, preloaded_relations).show? }

    statuses.each do |status|
      @account.bookmarks.find_or_create_by!(account: @account, status: status)
    end
  end

  def parse_import_data!(default_headers)
    data = CSV.parse(import_data, headers: true)
    data = CSV.parse(import_data, headers: default_headers) unless data.headers&.first&.strip&.include?(' ')
    @data = data.compact_blank
  end

  def import_data
    Paperclip.io_adapters.for(@import.data).read.force_encoding(Encoding::UTF_8)
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe ImportService, type: :service do
  include RoutingHelper

  let!(:account) { Fabricate(:account, locked: false) }
  let!(:bob)     { Fabricate(:account, username: 'bob', locked: false) }
  let!(:eve)     { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false, protocol: :activitypub, inbox_url: 'https://example.com/inbox') }

  before do
    stub_request(:post, 'https://example.com/inbox').to_return(status: 200)
  end

  context 'when importing old-style list of muted users' do
    subject { described_class.new }

    let(:csv) { attachment_fixture('mute-imports.txt') }

    describe 'when no accounts are muted' do
      let(:import) { Import.create(account: account, type: 'muting', data: csv) }

      it 'mutes the listed accounts, including notifications' do
        subject.call(import)
        expect(account.muting.count).to eq 2
        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
      end
    end

    describe 'when some accounts are muted and overwrite is not set' do
      let(:import) { Import.create(account: account, type: 'muting', data: csv) }

      it 'mutes the listed accounts, including notifications' do
        account.mute!(bob, notifications: false)
        subject.call(import)
        expect(account.muting.count).to eq 2
        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
      end
    end

    describe 'when some accounts are muted and overwrite is set' do
      let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) }

      it 'mutes the listed accounts, including notifications' do
        account.mute!(bob, notifications: false)
        subject.call(import)
        expect(account.muting.count).to eq 2
        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
      end
    end
  end

  context 'when importing new-style list of muted users' do
    subject { described_class.new }

    let(:csv) { attachment_fixture('new-mute-imports.txt') }

    describe 'when no accounts are muted' do
      let(:import) { Import.create(account: account, type: 'muting', data: csv) }

      it 'mutes the listed accounts, respecting notifications' do
        subject.call(import)
        expect(account.muting.count).to eq 2
        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
        expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
      end
    end

    describe 'when some accounts are muted and overwrite is not set' do
      let(:import) { Import.create(account: account, type: 'muting', data: csv) }

      it 'mutes the listed accounts, respecting notifications' do
        account.mute!(bob, notifications: true)
        subject.call(import)
        expect(account.muting.count).to eq 2
        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
        expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
      end
    end

    describe 'when some accounts are muted and overwrite is set' do
      let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) }

      it 'mutes the listed accounts, respecting notifications' do
        account.mute!(bob, notifications: true)
        subject.call(import)
        expect(account.muting.count).to eq 2
        expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
        expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
      end
    end
  end

  context 'when importing old-style list of followed users' do
    subject { described_class.new }

    let(:csv) { attachment_fixture('mute-imports.txt') }

    describe 'when no accounts are followed' do
      let(:import) { Import.create(account: account, type: 'following', data: csv) }

      it 'follows the listed accounts, including boosts' do
        subject.call(import)

        expect(account.following.count).to eq 1
        expect(account.follow_requests.count).to eq 1
        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
      end
    end

    describe 'when some accounts are already followed and overwrite is not set' do
      let(:import) { Import.create(account: account, type: 'following', data: csv) }

      it 'follows the listed accounts, including notifications' do
        account.follow!(bob, reblogs: false)
        subject.call(import)
        expect(account.following.count).to eq 1
        expect(account.follow_requests.count).to eq 1
        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
      end
    end

    describe 'when some accounts are already followed and overwrite is set' do
      let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }

      it 'mutes the listed accounts, including notifications' do
        account.follow!(bob, reblogs: false)
        subject.call(import)
        expect(account.following.count).to eq 1
        expect(account.follow_requests.count).to eq 1
        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
      end
    end
  end

  context 'when importing new-style list of followed users' do
    subject { described_class.new }

    let(:csv) { attachment_fixture('new-following-imports.txt') }

    describe 'when no accounts are followed' do
      let(:import) { Import.create(account: account, type: 'following', data: csv) }

      it 'follows the listed accounts, respecting boosts' do
        subject.call(import)
        expect(account.following.count).to eq 1
        expect(account.follow_requests.count).to eq 1
        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
        expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
      end
    end

    describe 'when some accounts are already followed and overwrite is not set' do
      let(:import) { Import.create(account: account, type: 'following', data: csv) }

      it 'mutes the listed accounts, respecting notifications' do
        account.follow!(bob, reblogs: true)
        subject.call(import)
        expect(account.following.count).to eq 1
        expect(account.follow_requests.count).to eq 1
        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
        expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
      end
    end

    describe 'when some accounts are already followed and overwrite is set' do
      let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }

      it 'mutes the listed accounts, respecting notifications' do
        account.follow!(bob, reblogs: true)
        subject.call(import)
        expect(account.following.count).to eq 1
        expect(account.follow_requests.count).to eq 1
        expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
        expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
      end
    end
  end

  # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users
  #
  # https://github.com/mastodon/mastodon/issues/20571
  context 'with a utf-8 encoded domains' do
    subject { described_class.new }

    let!(:nare) { Fabricate(:account, username: 'nare', domain: 'թութ.հայ', locked: false, protocol: :activitypub, inbox_url: 'https://թութ.հայ/inbox') }
    let(:csv) { attachment_fixture('utf8-followers.txt') }
    let(:import) { Import.create(account: account, type: 'following', data: csv) }

    # Make sure to not actually go to the remote server
    before do
      stub_request(:post, 'https://թութ.հայ/inbox').to_return(status: 200)
    end

    it 'follows the listed account' do
      expect(account.follow_requests.count).to eq 0
      subject.call(import)
      expect(account.follow_requests.count).to eq 1
    end
  end

  context 'when importing bookmarks' do
    subject { described_class.new }

    let(:csv) { attachment_fixture('bookmark-imports.txt') }
    let(:local_account)  { Fabricate(:account, username: 'foo', domain: '') }
    let!(:remote_status) { Fabricate(:status, uri: 'https://example.com/statuses/1312') }
    let!(:direct_status) { Fabricate(:status, uri: 'https://example.com/statuses/direct', visibility: :direct) }

    around do |example|
      local_before = Rails.configuration.x.local_domain
      web_before = Rails.configuration.x.web_domain
      Rails.configuration.x.local_domain = 'local.com'
      Rails.configuration.x.web_domain = 'local.com'
      example.run
      Rails.configuration.x.web_domain = web_before
      Rails.configuration.x.local_domain = local_before
    end

    before do
      service = instance_double(ActivityPub::FetchRemoteStatusService)
      allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service)
      allow(service).to receive(:call).with('https://unknown-remote.com/users/bar/statuses/1') do
        Fabricate(:status, uri: 'https://unknown-remote.com/users/bar/statuses/1')
      end
    end

    describe 'when no bookmarks are set' do
      let(:import) { Import.create(account: account, type: 'bookmarks', data: csv) }

      it 'adds the toots the user has access to to bookmarks' do
        local_status = Fabricate(:status, account: local_account, uri: 'https://local.com/users/foo/statuses/42', id: 42, local: true)
        subject.call(import)
        expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(local_status.id)
        expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(remote_status.id)
        expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to_not include(direct_status.id)
        expect(account.bookmarks.count).to eq 3
      end
    end
  end
end


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

class AuthorizeFollowService < BaseService
  include Payloadable

  def call(source_account, target_account, **options)
    if options[:skip_follow_request]
      follow_request = FollowRequest.new(account: source_account, target_account: target_account, uri: options[:follow_request_uri])
    else
      follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account)
      follow_request.authorize!
    end

    create_notification(follow_request) if !source_account.local? && source_account.activitypub?
    follow_request
  end

  private

  def create_notification(follow_request)
    ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), follow_request.target_account_id, follow_request.account.inbox_url)
  end

  def build_json(follow_request)
    Oj.dump(serialize_payload(follow_request, ActivityPub::AcceptFollowSerializer))
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe AuthorizeFollowService, type: :service do
  subject { described_class.new }

  let(:sender) { Fabricate(:account, username: 'alice') }

  describe 'local' do
    let(:bob) { Fabricate(:account, username: 'bob') }

    before do
      FollowRequest.create(account: bob, target_account: sender)
      subject.call(bob, sender)
    end

    it 'removes follow request' do
      expect(bob.requested?(sender)).to be false
    end

    it 'creates follow relation' do
      expect(bob.following?(sender)).to be true
    end
  end

  describe 'remote ActivityPub' do
    let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }

    before do
      FollowRequest.create(account: bob, target_account: sender)
      stub_request(:post, bob.inbox_url).to_return(status: 200)
      subject.call(bob, sender)
    end

    it 'removes follow request' do
      expect(bob.requested?(sender)).to be false
    end

    it 'creates follow relation' do
      expect(bob.following?(sender)).to be true
    end

    it 'sends an accept activity' do
      expect(a_request(:post, bob.inbox_url)).to have_been_made.once
    end
  end
end


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

class PrecomputeFeedService < BaseService
  include Redisable

  def call(account)
    FeedManager.instance.populate_home(account)
  ensure
    redis.del("account:#{account.id}:regeneration")
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe PrecomputeFeedService, type: :service do
  subject { described_class.new }

  describe 'call' do
    let(:account) { Fabricate(:account) }

    it 'fills a user timeline with statuses' do
      account = Fabricate(:account)
      status = Fabricate(:status, account: account)

      subject.call(account)

      expect(redis.zscore(FeedManager.instance.key(:home, account.id), status.id)).to be_within(0.1).of(status.id.to_f)
    end

    it 'does not raise an error even if it could not find any status' do
      account = Fabricate(:account)
      expect { subject.call(account) }.to_not raise_error
    end

    it 'filters statuses' do
      account = Fabricate(:account)
      muted_account = Fabricate(:account)
      Fabricate(:mute, account: account, target_account: muted_account)
      reblog = Fabricate(:status, account: muted_account)
      Fabricate(:status, account: account, reblog: reblog)

      subject.call(account)

      expect(redis.zscore(FeedManager.instance.key(:home, account.id), reblog.id)).to be_nil
    end
  end
end


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

class PostStatusService < BaseService
  include Redisable
  include LanguagesHelper

  MIN_SCHEDULE_OFFSET = 5.minutes.freeze

  class UnexpectedMentionsError < StandardError
    attr_reader :accounts

    def initialize(message, accounts)
      super(message)
      @accounts = accounts
    end
  end

  # Post a text status update, fetch and notify remote users mentioned
  # @param [Account] account Account from which to post
  # @param [Hash] options
  # @option [String] :text Message
  # @option [Status] :thread Optional status to reply to
  # @option [Boolean] :sensitive
  # @option [String] :visibility
  # @option [String] :spoiler_text
  # @option [String] :language
  # @option [String] :scheduled_at
  # @option [Hash] :poll Optional poll to attach
  # @option [Enumerable] :media_ids Optional array of media IDs to attach
  # @option [Doorkeeper::Application] :application
  # @option [String] :idempotency Optional idempotency key
  # @option [Boolean] :with_rate_limit
  # @option [Enumerable] :allowed_mentions Optional array of expected mentioned account IDs, raises `UnexpectedMentionsError` if unexpected accounts end up in mentions
  # @return [Status]
  def call(account, options = {})
    @account     = account
    @options     = options
    @text        = @options[:text] || ''
    @in_reply_to = @options[:thread]

    return idempotency_duplicate if idempotency_given? && idempotency_duplicate?

    validate_media!
    preprocess_attributes!

    if scheduled?
      schedule_status!
    else
      process_status!
    end

    redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given?

    unless scheduled?
      postprocess_status!
      bump_potential_friendship!
    end

    @status
  end

  private

  def preprocess_attributes!
    @sensitive    = (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?
    @text         = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
    @visibility   = @options[:visibility] || @account.user&.setting_default_privacy
    @visibility   = :unlisted if @visibility&.to_sym == :public && @account.silenced?
    @scheduled_at = @options[:scheduled_at]&.to_datetime
    @scheduled_at = nil if scheduled_in_the_past?
  rescue ArgumentError
    raise ActiveRecord::RecordInvalid
  end

  def process_status!
    @status = @account.statuses.new(status_attributes)
    process_mentions_service.call(@status, save_records: false)
    safeguard_mentions!(@status)

    # The following transaction block is needed to wrap the UPDATEs to
    # the media attachments when the status is created
    ApplicationRecord.transaction do
      @status.save!
    end
  end

  def safeguard_mentions!(status)
    return if @options[:allowed_mentions].nil?

    expected_account_ids = @options[:allowed_mentions].map(&:to_i)

    unexpected_accounts = status.mentions.map(&:account).to_a.reject { |mentioned_account| expected_account_ids.include?(mentioned_account.id) }
    return if unexpected_accounts.empty?

    raise UnexpectedMentionsError.new('Post would be sent to unexpected accounts', unexpected_accounts)
  end

  def schedule_status!
    status_for_validation = @account.statuses.build(status_attributes)

    if status_for_validation.valid?
      # Marking the status as destroyed is necessary to prevent the status from being
      # persisted when the associated media attachments get updated when creating the
      # scheduled status.
      status_for_validation.destroy

      # The following transaction block is needed to wrap the UPDATEs to
      # the media attachments when the scheduled status is created

      ApplicationRecord.transaction do
        @status = @account.scheduled_statuses.create!(scheduled_status_attributes)
      end
    else
      raise ActiveRecord::RecordInvalid
    end
  end

  def postprocess_status!
    process_hashtags_service.call(@status)
    Trends.tags.register(@status)
    LinkCrawlWorker.perform_async(@status.id)
    DistributionWorker.perform_async(@status.id)
    ActivityPub::DistributionWorker.perform_async(@status.id)
    PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll
  end

  def validate_media!
    if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
      @media = []
      return
    end

    raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 || @options[:poll].present?

    @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))

    raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:audio_or_video?)
    raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if @media.any?(&:not_processed?)
  end

  def process_mentions_service
    ProcessMentionsService.new
  end

  def process_hashtags_service
    ProcessHashtagsService.new
  end

  def scheduled?
    @scheduled_at.present?
  end

  def idempotency_key
    "idempotency:status:#{@account.id}:#{@options[:idempotency]}"
  end

  def idempotency_given?
    @options[:idempotency].present?
  end

  def idempotency_duplicate
    if scheduled?
      @account.schedule_statuses.find(@idempotency_duplicate)
    else
      @account.statuses.find(@idempotency_duplicate)
    end
  end

  def idempotency_duplicate?
    @idempotency_duplicate = redis.get(idempotency_key)
  end

  def scheduled_in_the_past?
    @scheduled_at.present? && @scheduled_at <= Time.now.utc + MIN_SCHEDULE_OFFSET
  end

  def bump_potential_friendship!
    return if !@status.reply? || @account.id == @status.in_reply_to_account_id

    ActivityTracker.increment('activity:interactions')
    return if @account.following?(@status.in_reply_to_account_id)

    PotentialFriendshipTracker.record(@account.id, @status.in_reply_to_account_id, :reply)
  end

  def status_attributes
    {
      text: @text,
      media_attachments: @media || [],
      ordered_media_attachment_ids: (@options[:media_ids] || []).map(&:to_i) & @media.map(&:id),
      thread: @in_reply_to,
      poll_attributes: poll_attributes,
      sensitive: @sensitive,
      spoiler_text: @options[:spoiler_text] || '',
      visibility: @visibility,
      language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale),
      application: @options[:application],
      rate_limit: @options[:with_rate_limit],
    }.compact
  end

  def scheduled_status_attributes
    {
      scheduled_at: @scheduled_at,
      media_attachments: @media || [],
      params: scheduled_options,
    }
  end

  def poll_attributes
    return if @options[:poll].blank?

    @options[:poll].merge(account: @account, voters_count: 0)
  end

  def scheduled_options
    @options.tap do |options_hash|
      options_hash[:in_reply_to_id]  = options_hash.delete(:thread)&.id
      options_hash[:application_id]  = options_hash.delete(:application)&.id
      options_hash[:scheduled_at]    = nil
      options_hash[:idempotency]     = nil
      options_hash[:with_rate_limit] = false
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe PostStatusService, type: :service do
  subject { described_class.new }

  it 'creates a new status' do
    account = Fabricate(:account)
    text = 'test status update'

    status = subject.call(account, text: text)

    expect(status).to be_persisted
    expect(status.text).to eq text
  end

  it 'creates a new response status' do
    in_reply_to_status = Fabricate(:status)
    account = Fabricate(:account)
    text = 'test status update'

    status = subject.call(account, text: text, thread: in_reply_to_status)

    expect(status).to be_persisted
    expect(status.text).to eq text
    expect(status.thread).to eq in_reply_to_status
  end

  context 'when scheduling a status' do
    let!(:account)         { Fabricate(:account) }
    let!(:future)          { Time.now.utc + 2.hours }
    let!(:previous_status) { Fabricate(:status, account: account) }

    it 'schedules a status' do
      status = subject.call(account, text: 'Hi future!', scheduled_at: future)
      expect(status).to be_a ScheduledStatus
      expect(status.scheduled_at).to eq future
      expect(status.params['text']).to eq 'Hi future!'
    end

    it 'does not immediately create a status' do
      media = Fabricate(:media_attachment, account: account)
      status = subject.call(account, text: 'Hi future!', media_ids: [media.id], scheduled_at: future)

      expect(status).to be_a ScheduledStatus
      expect(status.scheduled_at).to eq future
      expect(status.params['text']).to eq 'Hi future!'
      expect(status.params['media_ids']).to eq [media.id]
      expect(media.reload.status).to be_nil
      expect(Status.where(text: 'Hi future!')).to_not exist
    end

    it 'does not change statuses count' do
      expect { subject.call(account, text: 'Hi future!', scheduled_at: future, thread: previous_status) }.to_not(change { [account.statuses_count, previous_status.replies_count] })
    end
  end

  it 'creates response to the original status of boost' do
    boosted_status = Fabricate(:status)
    in_reply_to_status = Fabricate(:status, reblog: boosted_status)
    account = Fabricate(:account)
    text = 'test status update'

    status = subject.call(account, text: text, thread: in_reply_to_status)

    expect(status).to be_persisted
    expect(status.text).to eq text
    expect(status.thread).to eq boosted_status
  end

  it 'creates a sensitive status' do
    status = create_status_with_options(sensitive: true)

    expect(status).to be_persisted
    expect(status).to be_sensitive
  end

  it 'creates a status with spoiler text' do
    spoiler_text = 'spoiler text'

    status = create_status_with_options(spoiler_text: spoiler_text)

    expect(status).to be_persisted
    expect(status.spoiler_text).to eq spoiler_text
  end

  it 'creates a sensitive status when there is a CW but no text' do
    status = subject.call(Fabricate(:account), text: '', spoiler_text: 'foo')

    expect(status).to be_persisted
    expect(status).to be_sensitive
  end

  it 'creates a status with empty default spoiler text' do
    status = create_status_with_options(spoiler_text: nil)

    expect(status).to be_persisted
    expect(status.spoiler_text).to eq ''
  end

  it 'creates a status with the given visibility' do
    status = create_status_with_options(visibility: :private)

    expect(status).to be_persisted
    expect(status.visibility).to eq 'private'
  end

  it 'creates a status with limited visibility for silenced users' do
    status = subject.call(Fabricate(:account, silenced: true), text: 'test', visibility: :public)

    expect(status).to be_persisted
    expect(status.visibility).to eq 'unlisted'
  end

  it 'creates a status for the given application' do
    application = Fabricate(:application)

    status = create_status_with_options(application: application)

    expect(status).to be_persisted
    expect(status.application).to eq application
  end

  it 'creates a status with a language set' do
    account = Fabricate(:account)
    text = 'This is an English text.'

    status = subject.call(account, text: text)

    expect(status.language).to eq 'en'
  end

  it 'processes mentions' do
    mention_service = instance_double(ProcessMentionsService)
    allow(mention_service).to receive(:call)
    allow(ProcessMentionsService).to receive(:new).and_return(mention_service)
    account = Fabricate(:account)

    status = subject.call(account, text: 'test status update')

    expect(ProcessMentionsService).to have_received(:new)
    expect(mention_service).to have_received(:call).with(status, save_records: false)
  end

  it 'safeguards mentions' do
    account = Fabricate(:account)
    mentioned_account = Fabricate(:account, username: 'alice')
    unexpected_mentioned_account = Fabricate(:account, username: 'bob')

    expect do
      subject.call(account, text: '@alice hm, @bob is really annoying lately', allowed_mentions: [mentioned_account.id])
    end.to raise_error(an_instance_of(PostStatusService::UnexpectedMentionsError).and(having_attributes(accounts: [unexpected_mentioned_account])))
  end

  it 'processes duplicate mentions correctly' do
    account = Fabricate(:account)
    Fabricate(:account, username: 'alice')

    expect do
      subject.call(account, text: '@alice @alice @alice hey @alice')
    end.to_not raise_error
  end

  it 'processes hashtags' do
    hashtags_service = instance_double(ProcessHashtagsService)
    allow(hashtags_service).to receive(:call)
    allow(ProcessHashtagsService).to receive(:new).and_return(hashtags_service)
    account = Fabricate(:account)

    status = subject.call(account, text: 'test status update')

    expect(ProcessHashtagsService).to have_received(:new)
    expect(hashtags_service).to have_received(:call).with(status)
  end

  it 'gets distributed' do
    allow(DistributionWorker).to receive(:perform_async)
    allow(ActivityPub::DistributionWorker).to receive(:perform_async)

    account = Fabricate(:account)

    status = subject.call(account, text: 'test status update')

    expect(DistributionWorker).to have_received(:perform_async).with(status.id)
    expect(ActivityPub::DistributionWorker).to have_received(:perform_async).with(status.id)
  end

  it 'crawls links' do
    allow(LinkCrawlWorker).to receive(:perform_async)
    account = Fabricate(:account)

    status = subject.call(account, text: 'test status update')

    expect(LinkCrawlWorker).to have_received(:perform_async).with(status.id)
  end

  it 'attaches the given media to the created status' do
    account = Fabricate(:account)
    media = Fabricate(:media_attachment, account: account)

    status = subject.call(
      account,
      text: 'test status update',
      media_ids: [media.id]
    )

    expect(media.reload.status).to eq status
  end

  it 'does not attach media from another account to the created status' do
    account = Fabricate(:account)
    media = Fabricate(:media_attachment, account: Fabricate(:account))

    subject.call(
      account,
      text: 'test status update',
      media_ids: [media.id]
    )

    expect(media.reload.status).to be_nil
  end

  it 'does not allow attaching more than 4 files' do
    account = Fabricate(:account)

    expect do
      subject.call(
        account,
        text: 'test status update',
        media_ids: [
          Fabricate(:media_attachment, account: account),
          Fabricate(:media_attachment, account: account),
          Fabricate(:media_attachment, account: account),
          Fabricate(:media_attachment, account: account),
          Fabricate(:media_attachment, account: account),
        ].map(&:id)
      )
    end.to raise_error(
      Mastodon::ValidationError,
      I18n.t('media_attachments.validations.too_many')
    )
  end

  it 'does not allow attaching both videos and images' do
    account = Fabricate(:account)
    video   = Fabricate(:media_attachment, type: :video, account: account)
    image   = Fabricate(:media_attachment, type: :image, account: account)

    video.update(type: :video)

    expect do
      subject.call(
        account,
        text: 'test status update',
        media_ids: [
          video,
          image,
        ].map(&:id)
      )
    end.to raise_error(
      Mastodon::ValidationError,
      I18n.t('media_attachments.validations.images_and_video')
    )
  end

  it 'returns existing status when used twice with idempotency key' do
    account = Fabricate(:account)
    status1 = subject.call(account, text: 'test', idempotency: 'meepmeep')
    status2 = subject.call(account, text: 'test', idempotency: 'meepmeep')
    expect(status2.id).to eq status1.id
  end

  def create_status_with_options(**options)
    subject.call(Fabricate(:account), options.merge(text: 'test'))
  end
end


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

class AccountSearchService < BaseService
  attr_reader :query, :limit, :offset, :options, :account

  MENTION_ONLY_RE = /\A#{Account::MENTION_RE}\z/i

  # Min. number of characters to look for non-exact matches
  MIN_QUERY_LENGTH = 5

  class QueryBuilder
    def initialize(query, account, options = {})
      @query = query
      @account = account
      @options = options
    end

    def build
      AccountsIndex.query(
        bool: {
          must: {
            function_score: {
              query: {
                bool: {
                  must: must_clauses,
                },
              },

              functions: [
                reputation_score_function,
                followers_score_function,
                time_distance_function,
              ],
            },
          },

          should: should_clauses,
        }
      )
    end

    private

    def must_clauses
      if @account && @options[:following]
        [core_query, only_following_query]
      else
        [core_query]
      end
    end

    def should_clauses
      if @account && !@options[:following]
        [boost_following_query]
      else
        []
      end
    end

    # This function limits results to only the accounts the user is following
    def only_following_query
      {
        terms: {
          id: following_ids,
        },
      }
    end

    # This function promotes accounts the user is following
    def boost_following_query
      {
        terms: {
          id: following_ids,
          boost: 100,
        },
      }
    end

    # This function deranks accounts that follow more people than follow them
    def reputation_score_function
      {
        script_score: {
          script: {
            source: "(Math.max(doc['followers_count'].value, 0) + 0.0) / (Math.max(doc['followers_count'].value, 0) + Math.max(doc['following_count'].value, 0) + 1)",
          },
        },
      }
    end

    # This function promotes accounts that have more followers
    def followers_score_function
      {
        script_score: {
          script: {
            source: "(Math.max(doc['followers_count'].value, 0) / (Math.max(doc['followers_count'].value, 0) + 1))",
          },
        },
      }
    end

    # This function deranks accounts that haven't posted in a long time
    def time_distance_function
      {
        gauss: {
          last_status_at: {
            scale: '30d',
            offset: '30d',
            decay: 0.3,
          },
        },
      }
    end

    def following_ids
      @following_ids ||= @account.active_relationships.pluck(:target_account_id) + [@account.id]
    end
  end

  class AutocompleteQueryBuilder < QueryBuilder
    private

    def core_query
      {
        multi_match: {
          query: @query,
          type: 'bool_prefix',
          fields: %w(username^2 username.*^2 display_name display_name.*),
        },
      }
    end
  end

  class FullQueryBuilder < QueryBuilder
    private

    def core_query
      {
        multi_match: {
          query: @query,
          type: 'most_fields',
          fields: %w(username^2 display_name^2 text text.*),
          operator: 'and',
        },
      }
    end
  end

  def call(query, account = nil, options = {})
    @query   = query&.strip&.gsub(/\A@/, '')
    @limit   = options[:limit].to_i
    @offset  = options[:offset].to_i
    @options = options
    @account = account

    search_service_results.compact.uniq
  end

  private

  def search_service_results
    return [] if query.blank? || limit < 1

    [exact_match] + search_results
  end

  def exact_match
    return unless offset.zero? && username_complete?

    return @exact_match if defined?(@exact_match)

    match = if options[:resolve]
              ResolveAccountService.new.call(query)
            elsif domain_is_local?
              Account.find_local(query_username)
            else
              Account.find_remote(query_username, query_domain)
            end

    match = nil if !match.nil? && !account.nil? && options[:following] && !account.following?(match)

    @exact_match = match
  end

  def search_results
    return [] if limit_for_non_exact_results.zero?

    @search_results ||= begin
      results = from_elasticsearch if Chewy.enabled?
      results ||= from_database
      results
    end
  end

  def from_database
    if account
      advanced_search_results
    else
      simple_search_results
    end
  end

  def advanced_search_results
    Account.advanced_search_for(terms_for_query, account, limit: limit_for_non_exact_results, following: options[:following], offset: offset)
  end

  def simple_search_results
    Account.search_for(terms_for_query, limit: limit_for_non_exact_results, offset: offset)
  end

  def from_elasticsearch
    query_builder = begin
      if options[:use_searchable_text]
        FullQueryBuilder.new(terms_for_query, account, options.slice(:following))
      else
        AutocompleteQueryBuilder.new(terms_for_query, account, options.slice(:following))
      end
    end

    records = query_builder.build.limit(limit_for_non_exact_results).offset(offset).objects.compact

    ActiveRecord::Associations::Preloader.new(records: records, associations: :account_stat)

    records
  rescue Faraday::ConnectionFailed, Parslet::ParseFailed
    nil
  end

  def limit_for_non_exact_results
    return 0 if @account.nil? && query.size < MIN_QUERY_LENGTH

    if exact_match?
      limit - 1
    else
      limit
    end
  end

  def terms_for_query
    if domain_is_local?
      query_username
    else
      query
    end
  end

  def split_query_string
    @split_query_string ||= query.split('@')
  end

  def query_username
    @query_username ||= split_query_string.first || ''
  end

  def query_domain
    @query_domain ||= query_without_split? ? nil : split_query_string.last
  end

  def query_without_split?
    split_query_string.size == 1
  end

  def domain_is_local?
    @domain_is_local ||= TagManager.instance.local_domain?(query_domain)
  end

  def exact_match?
    exact_match.present?
  end

  def username_complete?
    query.include?('@') && "@#{query}".match?(MENTION_ONLY_RE)
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

describe AccountSearchService, type: :service do
  describe '#call' do
    context 'with a query to ignore' do
      it 'returns empty array for missing query' do
        results = subject.call('', nil, limit: 10)

        expect(results).to eq []
      end

      it 'returns empty array for limit zero' do
        Fabricate(:account, username: 'match')

        results = subject.call('match', nil, limit: 0)

        expect(results).to eq []
      end
    end

    context 'when searching for a simple term that is not an exact match' do
      it 'does not return a nil entry in the array for the exact match' do
        account = Fabricate(:account, username: 'matchingusername')
        results = subject.call('match', nil, limit: 5)

        expect(results).to eq [account]
      end
    end

    context 'when there is a local domain' do
      around do |example|
        before = Rails.configuration.x.local_domain

        example.run

        Rails.configuration.x.local_domain = before
      end

      it 'returns exact match first' do
        remote     = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e')
        remote_too = Fabricate(:account, username: 'b', domain: 'remote', display_name: 'e')
        exact      = Fabricate(:account, username: 'e')

        Rails.configuration.x.local_domain = 'example.com'

        results = subject.call('e@example.com', nil, limit: 2)

        expect(results).to eq([exact, remote]).or eq([exact, remote_too])
      end
    end

    context 'when there is a domain but no exact match' do
      it 'follows the remote account when resolve is true' do
        service = instance_double(ResolveAccountService, call: nil)
        allow(ResolveAccountService).to receive(:new).and_return(service)

        subject.call('newuser@remote.com', nil, limit: 10, resolve: true)
        expect(service).to have_received(:call).with('newuser@remote.com')
      end

      it 'does not follow the remote account when resolve is false' do
        service = instance_double(ResolveAccountService, call: nil)
        allow(ResolveAccountService).to receive(:new).and_return(service)

        subject.call('newuser@remote.com', nil, limit: 10, resolve: false)
        expect(service).to_not have_received(:call)
      end
    end

    it 'returns the fuzzy match first, and does not return suspended exacts' do
      partial = Fabricate(:account, username: 'exactness')
      Fabricate(:account, username: 'exact', suspended: true)
      results = subject.call('exact', nil, limit: 10)

      expect(results.size).to eq 1
      expect(results).to eq [partial]
    end

    it 'does not return suspended remote accounts' do
      Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true)
      results = subject.call('a@example.com', nil, limit: 2)

      expect(results.size).to eq 0
      expect(results).to eq []
    end
  end
end


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

class SoftwareUpdateCheckService < BaseService
  def call
    clean_outdated_updates!
    return unless SoftwareUpdate.check_enabled?

    process_update_notices!(fetch_update_notices)
  end

  private

  def clean_outdated_updates!
    SoftwareUpdate.find_each do |software_update|
      software_update.delete if Mastodon::Version.gem_version >= software_update.gem_version
    rescue ArgumentError
      software_update.delete
    end
  end

  def fetch_update_notices
    Request.new(:get, "#{api_url}?version=#{version}").add_headers('Accept' => 'application/json', 'User-Agent' => 'Mastodon update checker').perform do |res|
      return Oj.load(res.body_with_limit, mode: :strict) if res.code == 200
    end
  rescue HTTP::Error, OpenSSL::SSL::SSLError, Oj::ParseError
    nil
  end

  def api_url
    ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check')
  end

  def version
    @version ||= Mastodon::Version.to_s.split('+')[0]
  end

  def process_update_notices!(update_notices)
    return if update_notices.blank? || update_notices['updatesAvailable'].nil?

    # Clear notices that are not listed by the update server anymore
    SoftwareUpdate.where.not(version: update_notices['updatesAvailable'].pluck('version')).delete_all

    return if update_notices['updatesAvailable'].blank?

    # Check if any of the notices is new, and issue notifications
    known_versions = SoftwareUpdate.where(version: update_notices['updatesAvailable'].pluck('version')).pluck(:version)
    new_update_notices = update_notices['updatesAvailable'].filter { |notice| known_versions.exclude?(notice['version']) }
    return if new_update_notices.blank?

    new_updates = new_update_notices.map do |notice|
      SoftwareUpdate.create!(version: notice['version'], urgent: notice['urgent'], type: notice['type'], release_notes: notice['releaseNotes'])
    end

    notify_devops!(new_updates)
  end

  def should_notify_user?(user, urgent_version, patch_version)
    case user.settings['notification_emails.software_updates']
    when 'none'
      false
    when 'critical'
      urgent_version
    when 'patch'
      urgent_version || patch_version
    when 'all'
      true
    end
  end

  def notify_devops!(new_updates)
    has_new_urgent_version = new_updates.any?(&:urgent?)
    has_new_patch_version  = new_updates.any?(&:patch_type?)

    User.those_who_can(:view_devops).includes(:account).find_each do |user|
      next unless should_notify_user?(user, has_new_urgent_version, has_new_patch_version)

      if has_new_urgent_version
        AdminMailer.with(recipient: user.account).new_critical_software_updates.deliver_later
      else
        AdminMailer.with(recipient: user.account).new_software_updates.deliver_later
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe SoftwareUpdateCheckService, type: :service do
  subject { described_class.new }

  shared_examples 'when the feature is enabled' do
    let(:full_update_check_url) { "#{update_check_url}?version=#{Mastodon::Version.to_s.split('+')[0]}" }

    let(:devops_role)     { Fabricate(:user_role, name: 'DevOps', permissions: UserRole::FLAGS[:view_devops]) }
    let(:owner_user)      { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) }
    let(:old_devops_user) { Fabricate(:user) }
    let(:none_user)       { Fabricate(:user, role: devops_role) }
    let(:patch_user)      { Fabricate(:user, role: devops_role) }
    let(:critical_user)   { Fabricate(:user, role: devops_role) }

    around do |example|
      queue_adapter = ActiveJob::Base.queue_adapter
      ActiveJob::Base.queue_adapter = :test

      example.run

      ActiveJob::Base.queue_adapter = queue_adapter
    end

    before do
      Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: false)
      Fabricate(:software_update, version: '42.13.12', type: 'major', urgent: false)

      owner_user.settings.update('notification_emails.software_updates': 'all')
      owner_user.save!

      old_devops_user.settings.update('notification_emails.software_updates': 'all')
      old_devops_user.save!

      none_user.settings.update('notification_emails.software_updates': 'none')
      none_user.save!

      patch_user.settings.update('notification_emails.software_updates': 'patch')
      patch_user.save!

      critical_user.settings.update('notification_emails.software_updates': 'critical')
      critical_user.save!
    end

    context 'when the update server errors out' do
      before do
        stub_request(:get, full_update_check_url).to_return(status: 404)
      end

      it 'deletes outdated update records but keeps valid update records' do
        expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['42.13.12'])
      end
    end

    context 'when the server returns new versions' do
      let(:server_json) do
        {
          updatesAvailable: [
            {
              version: '4.2.1',
              urgent: false,
              type: 'patch',
              releaseNotes: 'https://github.com/mastodon/mastodon/releases/v4.2.1',
            },
            {
              version: '4.3.0',
              urgent: false,
              type: 'minor',
              releaseNotes: 'https://github.com/mastodon/mastodon/releases/v4.3.0',
            },
            {
              version: '5.0.0',
              urgent: false,
              type: 'minor',
              releaseNotes: 'https://github.com/mastodon/mastodon/releases/v5.0.0',
            },
          ],
        }
      end

      before do
        stub_request(:get, full_update_check_url).to_return(body: Oj.dump(server_json))
      end

      it 'updates the list of known updates' do
        expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['4.2.1', '4.3.0', '5.0.0'])
      end

      context 'when no update is urgent' do
        it 'sends e-mail notifications according to settings', :aggregate_failures do
          expect { subject.call }.to have_enqueued_mail(AdminMailer, :new_software_updates)
            .with(hash_including(params: { recipient: owner_user.account })).once
            .and(have_enqueued_mail(AdminMailer, :new_software_updates).with(hash_including(params: { recipient: patch_user.account })).once)
            .and(have_enqueued_mail.at_most(2))
        end
      end

      context 'when an update is urgent' do
        let(:server_json) do
          {
            updatesAvailable: [
              {
                version: '5.0.0',
                urgent: true,
                type: 'minor',
                releaseNotes: 'https://github.com/mastodon/mastodon/releases/v5.0.0',
              },
            ],
          }
        end

        it 'sends e-mail notifications according to settings', :aggregate_failures do
          expect { subject.call }.to have_enqueued_mail(AdminMailer, :new_critical_software_updates)
            .with(hash_including(params: { recipient: owner_user.account })).once
            .and(have_enqueued_mail(AdminMailer, :new_critical_software_updates).with(hash_including(params: { recipient: patch_user.account })).once)
            .and(have_enqueued_mail(AdminMailer, :new_critical_software_updates).with(hash_including(params: { recipient: critical_user.account })).once)
            .and(have_enqueued_mail.at_most(3))
        end
      end
    end
  end

  context 'when update checking is disabled' do
    around do |example|
      ClimateControl.modify UPDATE_CHECK_URL: '' do
        example.run
      end
    end

    before do
      Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: false)
    end

    it 'deletes outdated update records' do
      expect { subject.call }.to change(SoftwareUpdate, :count).from(1).to(0)
    end
  end

  context 'when using the default update checking API' do
    let(:update_check_url) { 'https://api.joinmastodon.org/update-check' }

    it_behaves_like 'when the feature is enabled'
  end

  context 'when using a custom update check URL' do
    let(:update_check_url) { 'https://api.example.com/update_check' }

    around do |example|
      ClimateControl.modify UPDATE_CHECK_URL: 'https://api.example.com/update_check' do
        example.run
      end
    end

    it_behaves_like 'when the feature is enabled'
  end
end


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

class UnsuspendAccountService < BaseService
  include Payloadable

  # Restores a recently-unsuspended account
  # @param [Account] account Account to restore
  def call(account)
    @account = account

    refresh_remote_account!

    return if @account.nil? || @account.suspended?

    merge_into_home_timelines!
    merge_into_list_timelines!
    publish_media_attachments!
    distribute_update_actor!
  end

  private

  def refresh_remote_account!
    return if @account.local?

    # While we had the remote account suspended, it could be that
    # it got suspended on its origin, too. So, we need to refresh
    # it straight away so it gets marked as remotely suspended in
    # that case.

    @account.update!(last_webfingered_at: nil)
    @account = ResolveAccountService.new.call(@account)

    # Worth noting that it is possible that the remote has not only
    # been suspended, but deleted permanently, in which case
    # @account would now be nil.
  end

  def distribute_update_actor!
    return unless @account.local?

    account_reach_finder = AccountReachFinder.new(@account)

    ActivityPub::DeliveryWorker.push_bulk(account_reach_finder.inboxes, limit: 1_000) do |inbox_url|
      [signed_activity_json, @account.id, inbox_url]
    end
  end

  def merge_into_home_timelines!
    @account.followers_for_local_distribution.reorder(nil).find_each do |follower|
      FeedManager.instance.merge_into_home(@account, follower)
    end
  end

  def merge_into_list_timelines!
    @account.lists_for_local_distribution.reorder(nil).find_each do |list|
      FeedManager.instance.merge_into_list(@account, list)
    end
  end

  def publish_media_attachments!
    attachment_names = MediaAttachment.attachment_definitions.keys

    @account.media_attachments.find_each do |media_attachment|
      attachment_names.each do |attachment_name|
        attachment = media_attachment.public_send(attachment_name)
        styles     = MediaAttachment::DEFAULT_STYLES | attachment.styles.keys

        next if attachment.blank?

        styles.each do |style|
          case Paperclip::Attachment.default_options[:storage]
          when :s3
            # Prevent useless S3 calls if ACLs are disabled
            next if ENV['S3_PERMISSION'] == ''

            begin
              attachment.s3_object(style).acl.put(acl: Paperclip::Attachment.default_options[:s3_permissions])
            rescue Aws::S3::Errors::NoSuchKey
              Rails.logger.warn "Tried to change acl on non-existent key #{attachment.s3_object(style).key}"
            rescue Aws::S3::Errors::NotImplemented => e
              Rails.logger.error "Error trying to change ACL on #{attachment.s3_object(style).key}: #{e.message}"
            end
          when :fog, :azure
            # Not supported
          when :filesystem
            begin
              FileUtils.chmod(0o666 & ~File.umask, attachment.path(style)) unless attachment.path(style).nil?
            rescue Errno::ENOENT
              Rails.logger.warn "Tried to change permission on non-existent file #{attachment.path(style)}"
            end
          end

          CacheBusterWorker.perform_async(attachment.path(style)) if Rails.configuration.x.cache_buster_enabled
        end
      end
    end
  end

  def signed_activity_json
    @signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateSerializer, signer: @account))
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe UnsuspendAccountService, type: :service do
  shared_context 'with common context' do
    subject { described_class.new.call(account) }

    let!(:local_follower) { Fabricate(:user, current_sign_in_at: 1.hour.ago).account }
    let!(:list)           { Fabricate(:list, account: local_follower) }

    before do
      allow(FeedManager.instance).to receive_messages(merge_into_home: nil, merge_into_list: nil)

      local_follower.follow!(account)
      list.accounts << account

      account.unsuspend!
    end
  end

  describe 'unsuspending a local account' do
    def match_update_actor_request(req, account)
      json = JSON.parse(req.body)
      actor_id = ActivityPub::TagManager.instance.uri_for(account)
      json['type'] == 'Update' && json['actor'] == actor_id && json['object']['id'] == actor_id && !json['object']['suspended']
    end

    before do
      stub_request(:post, 'https://alice.com/inbox').to_return(status: 201)
      stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
    end

    it 'does not change the “suspended” flag' do
      expect { subject }.to_not change(account, :suspended?)
    end

    include_examples 'with common context' do
      let!(:account)         { Fabricate(:account) }
      let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub, domain: 'alice.com') }
      let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') }
      let!(:report)          { Fabricate(:report, account: remote_reporter, target_account: account) }

      before do
        remote_follower.follow!(account)
      end

      it "merges back into local followers' feeds" do
        subject
        expect(FeedManager.instance).to have_received(:merge_into_home).with(account, local_follower)
        expect(FeedManager.instance).to have_received(:merge_into_list).with(account, list)
      end

      it 'sends an update actor to followers and reporters' do
        subject
        expect(a_request(:post, remote_follower.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
        expect(a_request(:post, remote_reporter.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
      end
    end
  end

  describe 'unsuspending a remote account' do
    include_examples 'with common context' do
      let!(:account)                 { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
      let!(:resolve_account_service) { instance_double(ResolveAccountService) }

      before do
        allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service)
      end

      context 'when the account is not remotely suspended' do
        before do
          allow(resolve_account_service).to receive(:call).with(account).and_return(account)
        end

        it 're-fetches the account' do
          subject
          expect(resolve_account_service).to have_received(:call).with(account)
        end

        it "merges back into local followers' feeds" do
          subject
          expect(FeedManager.instance).to have_received(:merge_into_home).with(account, local_follower)
          expect(FeedManager.instance).to have_received(:merge_into_list).with(account, list)
        end

        it 'does not change the “suspended” flag' do
          expect { subject }.to_not change(account, :suspended?)
        end
      end

      context 'when the account is remotely suspended' do
        before do
          allow(resolve_account_service).to receive(:call).with(account) do |account|
            account.suspend!(origin: :remote)
            account
          end
        end

        it 're-fetches the account' do
          subject
          expect(resolve_account_service).to have_received(:call).with(account)
        end

        it "does not merge back into local followers' feeds" do
          subject
          expect(FeedManager.instance).to_not have_received(:merge_into_home).with(account, local_follower)
          expect(FeedManager.instance).to_not have_received(:merge_into_list).with(account, list)
        end

        it 'marks account as suspended' do
          expect { subject }.to change(account, :suspended?).from(false).to(true)
        end
      end

      context 'when the account is remotely deleted' do
        before do
          allow(resolve_account_service).to receive(:call).with(account).and_return(nil)
        end

        it 're-fetches the account' do
          subject
          expect(resolve_account_service).to have_received(:call).with(account)
        end

        it "does not merge back into local followers' feeds" do
          subject
          expect(FeedManager.instance).to_not have_received(:merge_into_home).with(account, local_follower)
          expect(FeedManager.instance).to_not have_received(:merge_into_list).with(account, list)
        end
      end
    end
  end
end


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

class AfterBlockDomainFromAccountService < BaseService
  include Payloadable

  # This service does not create an AccountDomainBlock record,
  # it's meant to be called after such a record has been created
  # synchronously, to "clean up"
  def call(account, domain)
    @account = account
    @domain  = domain

    clear_notifications!
    remove_follows!
    reject_existing_followers!
    reject_pending_follow_requests!
  end

  private

  def remove_follows!
    @account.active_relationships.where(target_account: Account.where(domain: @domain)).includes(:target_account).reorder(nil).find_each do |follow|
      UnfollowService.new.call(@account, follow.target_account)
    end
  end

  def clear_notifications!
    Notification.where(account: @account).where(from_account: Account.where(domain: @domain)).in_batches.delete_all
  end

  def reject_existing_followers!
    @account.passive_relationships.where(account: Account.where(domain: @domain)).includes(:account).reorder(nil).find_each do |follow|
      reject_follow!(follow)
    end
  end

  def reject_pending_follow_requests!
    FollowRequest.where(target_account: @account).where(account: Account.where(domain: @domain)).includes(:account).reorder(nil).find_each do |follow_request|
      reject_follow!(follow_request)
    end
  end

  def reject_follow!(follow)
    follow.destroy

    return unless follow.account.activitypub?

    ActivityPub::DeliveryWorker.perform_async(Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer)), @account.id, follow.account.inbox_url)
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe AfterBlockDomainFromAccountService, type: :service do
  subject { described_class.new }

  let!(:wolf) { Fabricate(:account, username: 'wolf', domain: 'evil.org', inbox_url: 'https://evil.org/inbox', protocol: :activitypub) }
  let!(:alice) { Fabricate(:account, username: 'alice') }

  before do
    allow(ActivityPub::DeliveryWorker).to receive(:perform_async)
  end

  it 'purge followers from blocked domain' do
    wolf.follow!(alice)
    subject.call(alice, 'evil.org')
    expect(wolf.following?(alice)).to be false
  end

  it 'sends Reject->Follow to followers from blocked domain' do
    wolf.follow!(alice)
    subject.call(alice, 'evil.org')
    expect(ActivityPub::DeliveryWorker).to have_received(:perform_async).once
  end
end


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

class BlockDomainService < BaseService
  attr_reader :domain_block

  def call(domain_block, update = false)
    @domain_block = domain_block
    process_domain_block!
    process_retroactive_updates! if update
  end

  private

  def process_retroactive_updates!
    # If the domain block severity has been changed, undo the appropriate limitations
    scope = Account.by_domain_and_subdomains(domain_block.domain)

    scope.where(silenced_at: domain_block.created_at).in_batches.update_all(silenced_at: nil) unless domain_block.silence?
    scope.where(suspended_at: domain_block.created_at).in_batches.update_all(suspended_at: nil, suspension_origin: nil) unless domain_block.suspend?
  end

  def process_domain_block!
    if domain_block.silence?
      silence_accounts!
    elsif domain_block.suspend?
      suspend_accounts!
    end

    DomainClearMediaWorker.perform_async(domain_block.id) if domain_block.reject_media?
  end

  def silence_accounts!
    blocked_domain_accounts.without_silenced.in_batches.update_all(silenced_at: @domain_block.created_at)
  end

  def suspend_accounts!
    blocked_domain_accounts.without_suspended.in_batches.update_all(suspended_at: @domain_block.created_at, suspension_origin: :local)

    blocked_domain_accounts.where(suspended_at: @domain_block.created_at).reorder(nil).find_each do |account|
      DeleteAccountService.new.call(account, reserve_username: true, suspended_at: @domain_block.created_at)
    end
  end

  def blocked_domain
    domain_block.domain
  end

  def blocked_domain_accounts
    Account.by_domain_and_subdomains(blocked_domain)
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe BlockDomainService, type: :service do
  subject { described_class.new }

  let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') }
  let!(:bad_status_plain) { Fabricate(:status, account: bad_account, text: 'You suck') }
  let!(:bad_status_with_attachment) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
  let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status_with_attachment, file: attachment_fixture('attachment.jpg')) }
  let!(:already_banned_account) { Fabricate(:account, username: 'badguy', domain: 'evil.org', suspended: true, silenced: true) }

  describe 'for a suspension' do
    before do
      subject.call(DomainBlock.create!(domain: 'evil.org', severity: :suspend))
    end

    it 'creates a domain block' do
      expect(DomainBlock.blocked?('evil.org')).to be true
    end

    it 'removes remote accounts from that domain' do
      expect(Account.find_remote('badguy666', 'evil.org').suspended?).to be true
    end

    it 'records suspension date appropriately' do
      expect(Account.find_remote('badguy666', 'evil.org').suspended_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at
    end

    it 'keeps already-banned accounts banned' do
      expect(Account.find_remote('badguy', 'evil.org').suspended?).to be true
    end

    it 'does not overwrite suspension date of already-banned accounts' do
      expect(Account.find_remote('badguy', 'evil.org').suspended_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at
    end

    it 'removes the remote accounts\'s statuses and media attachments' do
      expect { bad_status_plain.reload }.to raise_exception ActiveRecord::RecordNotFound
      expect { bad_status_with_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound
      expect { bad_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound
    end
  end

  describe 'for a silence with reject media' do
    before do
      subject.call(DomainBlock.create!(domain: 'evil.org', severity: :silence, reject_media: true))
    end

    it 'does not create a domain block' do
      expect(DomainBlock.blocked?('evil.org')).to be false
    end

    it 'silences remote accounts from that domain' do
      expect(Account.find_remote('badguy666', 'evil.org').silenced?).to be true
    end

    it 'records suspension date appropriately' do
      expect(Account.find_remote('badguy666', 'evil.org').silenced_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at
    end

    it 'keeps already-banned accounts banned' do
      expect(Account.find_remote('badguy', 'evil.org').silenced?).to be true
    end

    it 'does not overwrite suspension date of already-banned accounts' do
      expect(Account.find_remote('badguy', 'evil.org').silenced_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at
    end

    it 'leaves the domains status and attachments, but clears media' do
      expect { bad_status_plain.reload }.to_not raise_error
      expect { bad_status_with_attachment.reload }.to_not raise_error
      expect { bad_attachment.reload }.to_not raise_error
      expect(bad_attachment.file.exists?).to be false
    end
  end
end


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

class BlockService < BaseService
  include Payloadable

  def call(account, target_account)
    return if account.id == target_account.id

    UnfollowService.new.call(account, target_account) if account.following?(target_account)
    UnfollowService.new.call(target_account, account) if target_account.following?(account)
    RejectFollowService.new.call(target_account, account) if target_account.requested?(account)

    block = account.block!(target_account)

    BlockWorker.perform_async(account.id, target_account.id)
    create_notification(block) if !target_account.local? && target_account.activitypub?
    block
  end

  private

  def create_notification(block)
    ActivityPub::DeliveryWorker.perform_async(build_json(block), block.account_id, block.target_account.inbox_url)
  end

  def build_json(block)
    Oj.dump(serialize_payload(block, ActivityPub::BlockSerializer))
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe BlockService, type: :service do
  subject { described_class.new }

  let(:sender) { Fabricate(:account, username: 'alice') }

  describe 'local' do
    let(:bob) { Fabricate(:account, username: 'bob') }

    before do
      subject.call(sender, bob)
    end

    it 'creates a blocking relation' do
      expect(sender.blocking?(bob)).to be true
    end
  end

  describe 'remote ActivityPub' do
    let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }

    before do
      stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
      subject.call(sender, bob)
    end

    it 'creates a blocking relation' do
      expect(sender.blocking?(bob)).to be true
    end

    it 'sends a block activity' do
      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
    end
  end
end


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

class UnfollowService < BaseService
  include Payloadable
  include Redisable
  include Lockable

  # Unfollow and notify the remote user
  # @param [Account] source_account Where to unfollow from
  # @param [Account] target_account Which to unfollow
  # @param [Hash] options
  # @option [Boolean] :skip_unmerge
  def call(source_account, target_account, options = {})
    @source_account = source_account
    @target_account = target_account
    @options        = options

    with_redis_lock("relationship:#{[source_account.id, target_account.id].sort.join(':')}") do
      unfollow! || undo_follow_request!
    end
  end

  private

  def unfollow!
    follow = Follow.find_by(account: @source_account, target_account: @target_account)

    return unless follow

    follow.destroy!

    create_notification(follow) if !@target_account.local? && @target_account.activitypub?
    create_reject_notification(follow) if @target_account.local? && !@source_account.local? && @source_account.activitypub?
    UnmergeWorker.perform_async(@target_account.id, @source_account.id) unless @options[:skip_unmerge]

    follow
  end

  def undo_follow_request!
    follow_request = FollowRequest.find_by(account: @source_account, target_account: @target_account)

    return unless follow_request

    follow_request.destroy!

    create_notification(follow_request) unless @target_account.local?

    follow_request
  end

  def create_notification(follow)
    ActivityPub::DeliveryWorker.perform_async(build_json(follow), follow.account_id, follow.target_account.inbox_url)
  end

  def create_reject_notification(follow)
    ActivityPub::DeliveryWorker.perform_async(build_reject_json(follow), follow.target_account_id, follow.account.inbox_url)
  end

  def build_json(follow)
    Oj.dump(serialize_payload(follow, ActivityPub::UndoFollowSerializer))
  end

  def build_reject_json(follow)
    Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer))
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe UnfollowService, type: :service do
  subject { described_class.new }

  let(:sender) { Fabricate(:account, username: 'alice') }

  describe 'local' do
    let(:bob) { Fabricate(:account, username: 'bob') }

    before do
      sender.follow!(bob)
      subject.call(sender, bob)
    end

    it 'destroys the following relation' do
      expect(sender.following?(bob)).to be false
    end
  end

  describe 'remote ActivityPub' do
    let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }

    before do
      sender.follow!(bob)
      stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
      subject.call(sender, bob)
    end

    it 'destroys the following relation' do
      expect(sender.following?(bob)).to be false
    end

    it 'sends an unfollow activity' do
      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
    end
  end

  describe 'remote ActivityPub (reverse)' do
    let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }

    before do
      bob.follow!(sender)
      stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
      subject.call(bob, sender)
    end

    it 'destroys the following relation' do
      expect(bob.following?(sender)).to be false
    end

    it 'sends a reject activity' do
      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
    end
  end
end


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

class SuspendAccountService < BaseService
  include Payloadable

  # Carry out the suspension of a recently-suspended account
  # @param [Account] account Account to suspend
  def call(account)
    return unless account.suspended?

    @account = account

    reject_remote_follows!
    distribute_update_actor!
    unmerge_from_home_timelines!
    unmerge_from_list_timelines!
    privatize_media_attachments!
  end

  private

  def reject_remote_follows!
    return if @account.local? || !@account.activitypub?

    # When suspending a remote account, the account obviously doesn't
    # actually become suspended on its origin server, i.e. unlike a
    # locally suspended account it continues to have access to its home
    # feed and other content. To prevent it from being able to continue
    # to access toots it would receive because it follows local accounts,
    # we have to force it to unfollow them. Unfortunately, there is no
    # counterpart to this operation, i.e. you can't then force a remote
    # account to re-follow you, so this part is not reversible.

    Follow.where(account: @account).find_in_batches do |follows|
      ActivityPub::DeliveryWorker.push_bulk(follows) do |follow|
        [Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer)), follow.target_account_id, @account.inbox_url]
      end

      follows.each(&:destroy)
    end
  end

  def distribute_update_actor!
    return unless @account.local?

    account_reach_finder = AccountReachFinder.new(@account)

    ActivityPub::DeliveryWorker.push_bulk(account_reach_finder.inboxes, limit: 1_000) do |inbox_url|
      [signed_activity_json, @account.id, inbox_url]
    end
  end

  def unmerge_from_home_timelines!
    @account.followers_for_local_distribution.reorder(nil).find_each do |follower|
      FeedManager.instance.unmerge_from_home(@account, follower)
    end
  end

  def unmerge_from_list_timelines!
    @account.lists_for_local_distribution.reorder(nil).find_each do |list|
      FeedManager.instance.unmerge_from_list(@account, list)
    end
  end

  def privatize_media_attachments!
    attachment_names = MediaAttachment.attachment_definitions.keys

    @account.media_attachments.find_each do |media_attachment|
      attachment_names.each do |attachment_name|
        attachment = media_attachment.public_send(attachment_name)
        styles     = MediaAttachment::DEFAULT_STYLES | attachment.styles.keys

        next if attachment.blank?

        styles.each do |style|
          case Paperclip::Attachment.default_options[:storage]
          when :s3
            # Prevent useless S3 calls if ACLs are disabled
            next if ENV['S3_PERMISSION'] == ''

            begin
              attachment.s3_object(style).acl.put(acl: 'private')
            rescue Aws::S3::Errors::NoSuchKey
              Rails.logger.warn "Tried to change acl on non-existent key #{attachment.s3_object(style).key}"
            rescue Aws::S3::Errors::NotImplemented => e
              Rails.logger.error "Error trying to change ACL on #{attachment.s3_object(style).key}: #{e.message}"
            end
          when :fog, :azure
            # Not supported
          when :filesystem
            begin
              FileUtils.chmod(0o600 & ~File.umask, attachment.path(style)) unless attachment.path(style).nil?
            rescue Errno::ENOENT
              Rails.logger.warn "Tried to change permission on non-existent file #{attachment.path(style)}"
            end
          end

          CacheBusterWorker.perform_async(attachment.path(style)) if Rails.configuration.x.cache_buster_enabled
        end
      end
    end
  end

  def signed_activity_json
    @signed_activity_json ||= Oj.dump(serialize_payload(@account, ActivityPub::UpdateSerializer, signer: @account))
  end
end

```

Response:
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe SuspendAccountService, type: :service do
  shared_examples 'common behavior' do
    subject { described_class.new.call(account) }

    let!(:local_follower) { Fabricate(:user, current_sign_in_at: 1.hour.ago).account }
    let!(:list)           { Fabricate(:list, account: local_follower) }

    before do
      allow(FeedManager.instance).to receive_messages(unmerge_from_home: nil, unmerge_from_list: nil)

      local_follower.follow!(account)
      list.accounts << account

      account.suspend!
    end

    it "unmerges from local followers' feeds" do
      subject
      expect(FeedManager.instance).to have_received(:unmerge_from_home).with(account, local_follower)
      expect(FeedManager.instance).to have_received(:unmerge_from_list).with(account, list)
    end

    it 'does not change the “suspended” flag' do
      expect { subject }.to_not change(account, :suspended?)
    end
  end

  describe 'suspending a local account' do
    def match_update_actor_request(req, account)
      json = JSON.parse(req.body)
      actor_id = ActivityPub::TagManager.instance.uri_for(account)
      json['type'] == 'Update' && json['actor'] == actor_id && json['object']['id'] == actor_id && json['object']['suspended']
    end

    before do
      stub_request(:post, 'https://alice.com/inbox').to_return(status: 201)
      stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
    end

    include_examples 'common behavior' do
      let!(:account)         { Fabricate(:account) }
      let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub, domain: 'alice.com') }
      let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') }
      let!(:report)          { Fabricate(:report, account: remote_reporter, target_account: account) }

      before do
        remote_follower.follow!(account)
      end

      it 'sends an update actor to followers and reporters' do
        subject
        expect(a_request(:post, remote_follower.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
        expect(a_request(:post, remote_reporter.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
      end
    end
  end

  describe 'suspending a remote account' do
    def match_reject_follow_request(req, account, followee)
      json = JSON.parse(req.body)
      json['type'] == 'Reject' && json['actor'] == ActivityPub::TagManager.instance.uri_for(followee) && json['object']['actor'] == account.uri
    end

    before do
      stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
    end

    include_examples 'common behavior' do
      let!(:account)        { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
      let!(:local_followee) { Fabricate(:account) }

      before do
        account.follow!(local_followee)
      end

      it 'sends a reject follow' do
        subject
        expect(a_request(:post, account.inbox_url).with { |req| match_reject_follow_request(req, account, local_followee) }).to have_been_made.once
      end
    end
  end
end