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

module MergeRequests
  class CreateFromIssueService < MergeRequests::CreateService
    # TODO: This constructor does not use the "params:" argument from the superclass,
    #   but instead has a custom "mr_params:" argument. This is because historically,
    #   prior to named arguments being introduced to the constructor, it never passed
    #   along the third positional argument when calling `super`.
    #   This should be changed, in order to be consistent (all subclasses should pass
    #   along all of the arguments to the superclass, otherwise it is probably not an
    #   "is a" relationship). However, we need to be sure that passing the params
    #   argument to `super` (especially target_project_id) will not cause any unexpected
    #   behavior in the superclass. Since the addition of the named arguments is
    #   intended to be a low-risk pure refactor, we will defer this fix
    #   to this follow-on issue:
    #   https://gitlab.com/gitlab-org/gitlab/-/issues/328726
    def initialize(project:, current_user:, mr_params: {})
      # branch - the name of new branch
      # ref    - the source of new branch.

      @branch_name       = mr_params[:branch_name]
      @issue_iid         = mr_params[:issue_iid]
      @ref               = mr_params[:ref]
      @target_project_id = mr_params[:target_project_id]

      super(project: project, current_user: current_user)
    end

    def execute
      return error('Project not found') if target_project.blank?
      return error('Not allowed to create merge request') unless can_create_merge_request?
      return error('Invalid issue iid') unless @issue_iid.present? && issue.present?

      result = ::Branches::CreateService.new(target_project, current_user).execute(branch_name, ref)
      return result if result[:status] == :error

      new_merge_request = create(merge_request)

      if new_merge_request.valid?
        merge_request_activity_counter.track_mr_create_from_issue(user: current_user)
        SystemNoteService.new_merge_request(issue, project, current_user, new_merge_request)

        success(new_merge_request)
      else
        SystemNoteService.new_issue_branch(issue, project, current_user, branch_name, branch_project: target_project)

        error(new_merge_request.errors)
      end
    end

    private

    def can_create_merge_request?
      can?(current_user, :create_merge_request_from, target_project)
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def issue
      @issue ||= IssuesFinder.new(current_user, project_id: project.id).find_by(iid: @issue_iid)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def branch_name
      @branch ||= @branch_name || issue.to_branch_name
    end

    def ref
      if valid_ref?
        @ref
      else
        default_branch
      end
    end

    def valid_ref?
      ref_is_branch? || ref_is_tag?
    end

    def ref_is_branch?
      target_project.repository.branch_exists?(@ref)
    end

    def ref_is_tag?
      target_project.repository.tag_exists?(@ref)
    end

    def default_branch
      target_project.default_branch_or_main
    end

    def merge_request
      MergeRequests::BuildService.new(project: target_project, current_user: current_user, params: merge_request_params).execute
    end

    def merge_request_params
      {
        issue_iid: @issue_iid,
        source_project_id: target_project.id,
        source_branch: branch_name,
        target_project_id: target_project.id,
        target_branch: target_branch,
        assignee_ids: [current_user.id]
      }
    end

    def target_branch
      if ref_is_branch?
        @ref
      else
        default_branch
      end
    end

    def success(merge_request)
      super().merge(merge_request: merge_request)
    end

    def target_project
      @target_project ||=
        if @target_project_id.present?
          project.forks.find_by_id(@target_project_id)
        else
          project
        end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::CreateFromIssueService, feature_category: :code_review_workflow do
  include ProjectForksHelper

  let(:project) { create(:project, :repository) }
  let(:user) { create(:user) }
  let(:label_ids) { create_pair(:label, project: project).map(&:id) }
  let(:milestone_id) { create(:milestone, project: project).id }
  let(:issue) { create(:issue, project: project, milestone_id: milestone_id) }
  let(:custom_source_branch) { 'custom-source-branch' }
  let(:service) { described_class.new(project: project, current_user: user, mr_params: service_params) }
  let(:service_with_custom_source_branch) { described_class.new(project: project, current_user: user, mr_params: { branch_name: custom_source_branch, **service_params }) }

  before do
    project.add_developer(user)
  end

  describe '#execute' do
    shared_examples_for 'a service that creates a merge request from an issue' do
      it 'returns an error when user can not create merge request on target project' do
        result = described_class.new(project: project, current_user: create(:user), mr_params: service_params).execute

        expect(result[:status]).to eq(:error)
        expect(result[:message]).to eq('Not allowed to create merge request')
      end

      it 'returns an error with invalid issue iid' do
        result = described_class.new(project: project, current_user: user, mr_params: { issue_iid: -1 }).execute

        expect(result[:status]).to eq(:error)
        expect(result[:message]).to eq('Invalid issue iid')
      end

      it 'creates a branch based on issue title' do
        service.execute

        expect(target_project.repository.branch_exists?(issue.to_branch_name)).to be_truthy
      end

      it 'creates a branch using passed name' do
        service_with_custom_source_branch.execute

        expect(target_project.repository.branch_exists?(custom_source_branch)).to be_truthy
      end

      it 'creates the new_merge_request system note' do
        expect(SystemNoteService).to receive(:new_merge_request).with(issue, project, user, instance_of(MergeRequest))

        service.execute
      end

      it 'tracks the mr creation when the mr is valid' do
        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .to receive(:track_mr_create_from_issue)
          .with(user: user)

        service.execute
      end

      it 'creates the new_issue_branch system note when the branch could be created but the merge_request cannot be created' do
        expect_next_instance_of(MergeRequest) do |instance|
          expect(instance).to receive(:valid?).at_least(:once).and_return(false)
        end

        expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, issue.to_branch_name, branch_project: target_project)

        service.execute
      end

      it 'does not track the mr creation when the Mr is invalid' do
        expect_next_instance_of(MergeRequest) do |instance|
          expect(instance).to receive(:valid?).at_least(:once).and_return(false)
        end

        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .not_to receive(:track_mr_create_from_issue)

        service.execute
      end

      it 'creates a merge request' do
        expect { service.execute }.to change(target_project.merge_requests, :count).by(1)
      end

      it 'sets the merge request author to current user and assigns them' do
        result = service.execute

        expect(result[:merge_request].author).to eq(user)
        expect(result[:merge_request].assignees).to eq([user])
      end

      it 'sets the merge request source branch to the new issue branch' do
        result = service.execute

        expect(result[:merge_request].source_branch).to eq(issue.to_branch_name)
      end

      it 'sets the merge request source branch to the passed branch name' do
        result = service_with_custom_source_branch.execute

        expect(result[:merge_request].source_branch).to eq(custom_source_branch)
      end

      it 'sets the merge request target branch to the project default branch' do
        result = service.execute

        expect(result[:merge_request].target_branch).to eq(target_project.default_branch)
      end

      it 'executes quick actions if the build service sets them in the description' do
        allow(service).to receive(:merge_request).and_wrap_original do |m, *args|
          m.call(*args).tap do |merge_request|
            merge_request.description = "/assign #{user.to_reference}"
          end
        end

        result = service.execute

        expect(result[:merge_request].assignees).to eq([user])
      end

      context 'when ref branch is set' do
        subject { described_class.new(project: project, current_user: user, mr_params: { ref: 'feature', **service_params }).execute }

        it 'sets the merge request source branch to the new issue branch' do
          expect(subject[:merge_request].source_branch).to eq(issue.to_branch_name)
        end

        it 'sets the merge request target branch to the ref branch' do
          expect(subject[:merge_request].target_branch).to eq('feature')
        end

        context 'when the ref is a tag' do
          subject { described_class.new(project: project, current_user: user, mr_params: { ref: 'v1.0.0', **service_params }).execute }

          it 'sets the merge request source branch to the new issue branch' do
            expect(subject[:merge_request].source_branch).to eq(issue.to_branch_name)
          end

          it 'creates a merge request' do
            expect { subject }.to change(target_project.merge_requests, :count).by(1)
          end

          it 'sets the merge request target branch to the project default branch' do
            expect(subject[:merge_request].target_branch).to eq(target_project.default_branch)
          end
        end

        context 'when ref branch does not exist' do
          subject { described_class.new(project: project, current_user: user, mr_params: { ref: 'no-such-branch', **service_params }).execute }

          it 'creates a merge request' do
            expect { subject }.to change(target_project.merge_requests, :count).by(1)
          end

          it 'sets the merge request target branch to the project default branch' do
            expect(subject[:merge_request].target_branch).to eq(target_project.default_branch)
          end
        end
      end
    end

    context 'no target_project_id specified' do
      let(:service_params) { { issue_iid: issue.iid } }
      let(:target_project) { project }

      it_behaves_like 'a service that creates a merge request from an issue'

      it "inherits labels" do
        issue.assign_attributes(label_ids: label_ids)

        result = service.execute

        expect(result[:merge_request].label_ids).to match_array(label_ids)
      end

      it "inherits milestones" do
        result = service.execute

        expect(result[:merge_request].milestone_id).to eq(milestone_id)
      end

      it 'sets the merge request title to: "Draft: Resolves "$issue-title"' do
        result = service.execute

        expect(result[:merge_request].title).to eq("Draft: Resolve \"#{issue.title}\"")
      end
    end

    context 'target_project_id is specified' do
      let(:service_params) { { issue_iid: issue.iid, target_project_id: target_project.id } }

      context 'target project is not a fork of the project' do
        let(:target_project) { create(:project, :repository) }

        it 'returns an error about not finding the project' do
          result = service.execute

          expect(result[:status]).to eq(:error)
          expect(result[:message]).to eq('Project not found')
        end

        it 'does not create merge request' do
          expect { service.execute }.to change(target_project.merge_requests, :count).by(0)
        end
      end

      context 'target project is a fork of project project' do
        let(:target_project) { fork_project(project, user, repository: true) }

        it_behaves_like 'a service that creates a merge request from an issue'

        it 'sets the merge request title to: "Draft: $issue-branch-name' do
          result = service.execute

          expect(result[:merge_request].title).to eq("Draft: #{issue.to_branch_name.titleize.humanize}")
        end
      end
    end
  end
end


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

module MergeRequests
  class UpdateAssigneesService < UpdateService
    # a stripped down service that only does what it must to update the
    # assignees, and knows that it does not have to check for other updates.
    # This saves a lot of queries for irrelevant things that cannot possibly
    # change in the execution of this service.
    def execute(merge_request)
      return merge_request unless current_user&.can?(:update_merge_request, merge_request)

      old_assignees = merge_request.assignees.to_a
      old_ids = old_assignees.map(&:id)
      new_ids = new_user_ids(merge_request, update_attrs[:assignee_ids], :assignees)

      return merge_request if merge_request.errors.any?
      return merge_request if new_ids.size != update_attrs[:assignee_ids].size
      return merge_request if old_ids.to_set == new_ids.to_set # no-change

      attrs = update_attrs.merge(assignee_ids: new_ids)

      merge_request.update(**attrs)

      return merge_request unless merge_request.valid?

      # Defer the more expensive operations (handle_assignee_changes) to the background
      MergeRequests::HandleAssigneesChangeService
        .new(project: project, current_user: current_user)
        .async_execute(merge_request, old_assignees, execute_hooks: true)

      merge_request
    end

    private

    def assignee_ids
      filter_sentinel_values(params.fetch(:assignee_ids)).first(1)
    end

    def params
      ps = super

      # allow either assignee_id or assignee_ids, preferring assignee_id if passed.
      { assignee_ids: ps.key?(:assignee_id) ? Array.wrap(ps[:assignee_id]) : ps[:assignee_ids] }
    end

    def update_attrs
      @attrs ||= { updated_at: Time.current, updated_by: current_user, assignee_ids: assignee_ids }
    end
  end
end

MergeRequests::UpdateAssigneesService.prepend_mod_with('MergeRequests::UpdateAssigneesService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::UpdateAssigneesService, feature_category: :code_review_workflow do
  include AfterNextHelpers

  let_it_be(:group) { create(:group, :public) }
  let_it_be(:project) { create(:project, :private, :repository, group: group) }
  let_it_be(:user) { create(:user) }
  let_it_be(:user2) { create(:user) }
  let_it_be(:user3) { create(:user) }

  let_it_be_with_reload(:merge_request) do
    create(
      :merge_request,
      :simple,
      :unique_branches,
      title: 'Old title',
      description: "FYI #{user2.to_reference}",
      assignee_ids: [user3.id],
      source_project: project,
      target_project: project,
      author: create(:user)
    )
  end

  before do
    project.add_maintainer(user)
    project.add_developer(user2)
    project.add_developer(user3)
    merge_request.errors.clear
  end

  let(:service) { described_class.new(project: project, current_user: user, params: opts) }
  let(:opts) { { assignee_ids: [user2.id] } }

  describe 'execute' do
    def update_merge_request
      service.execute(merge_request)
    end

    shared_examples 'it updates and enqueues the job' do
      it 'correctly updates the MR and enqueues the job' do
        expect_next(MergeRequests::HandleAssigneesChangeService, project: project, current_user: user) do |service|
          expect(service)
            .to receive(:async_execute).with(merge_request, [user3], execute_hooks: true)
        end

        expect { update_merge_request }
          .to change { merge_request.reload.assignees }.from([user3]).to(new_users)
          .and change(merge_request, :updated_at)
          .and change(merge_request, :updated_by).to(user)
      end
    end

    shared_examples 'removing all assignees' do
      it 'removes all assignees' do
        expect(update_merge_request).to have_attributes(assignees: be_empty, errors: be_none)
      end

      it 'enqueues the correct background work' do
        expect_next(MergeRequests::HandleAssigneesChangeService, project: project, current_user: user) do |service|
          expect(service)
            .to receive(:async_execute)
            .with(merge_request, [user3], execute_hooks: true)
        end

        update_merge_request
      end
    end

    context 'when the parameters are valid' do
      context 'when using sentinel values' do
        context 'when using assignee_ids' do
          let(:opts) { { assignee_ids: [0] } }

          it_behaves_like 'removing all assignees'
        end

        context 'when using assignee_id' do
          let(:opts) { { assignee_id: 0 } }

          it_behaves_like 'removing all assignees'
        end
      end

      context 'when the assignee_ids parameter is the empty list' do
        let(:opts) { { assignee_ids: [] } }

        it_behaves_like 'removing all assignees'
      end

      it_behaves_like 'it updates and enqueues the job' do
        let(:new_users) { [user2] }
      end

      it 'does not update the assignees if they do not have access' do
        opts[:assignee_ids] = [create(:user).id]

        expect(update_merge_request).to have_attributes(
          assignees: [user3],
          errors: be_any
        )
      end

      it 'is more efficient than using the full update-service' do
        allow_next(MergeRequests::HandleAssigneesChangeService, project: project, current_user: user) do |service|
          expect(service)
            .to receive(:async_execute)
            .with(merge_request, [user3], execute_hooks: true)
        end

        other_mr = create(
          :merge_request,
          :simple,
          :unique_branches,
          title: merge_request.title,
          description: merge_request.description,
          assignee_ids: merge_request.assignee_ids,
          source_project: merge_request.project,
          author: merge_request.author
        )

        update_service = ::MergeRequests::UpdateService.new(project: project, current_user: user, params: opts)

        expect { service.execute(merge_request) }
          .to issue_fewer_queries_than { update_service.execute(other_mr) }
      end
    end
  end
end


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

module MergeRequests
  class ReloadMergeHeadDiffService
    include BaseServiceUtility

    def initialize(merge_request)
      @merge_request = merge_request
    end

    def execute
      return error("Merge request has no merge ref head.") unless merge_request.merge_ref_head.present?

      error_msg = recreate_merge_head_diff

      return error(error_msg) if error_msg

      success
    end

    private

    attr_reader :merge_request

    def recreate_merge_head_diff
      merge_request.merge_head_diff&.destroy!

      # n+1: https://gitlab.com/gitlab-org/gitlab/-/issues/19377
      Gitlab::GitalyClient.allow_n_plus_1_calls do
        merge_request.create_merge_head_diff!
      end

      # Reset the merge request so it won't load the merge head diff as the
      # MergeRequest#merge_request_diff.
      merge_request.reset

      nil
    rescue StandardError => e
      message = "Failed to recreate merge head diff: #{e.message}"

      Gitlab::AppLogger.error(message: message, merge_request_id: merge_request.id)
      message
    end
  end
end

MergeRequests::ReloadMergeHeadDiffService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::ReloadMergeHeadDiffService, feature_category: :code_review_workflow do
  let(:merge_request) { create(:merge_request) }

  subject { described_class.new(merge_request).execute }

  describe '#execute' do
    before do
      MergeRequests::MergeToRefService
        .new(project: merge_request.project, current_user: merge_request.author)
        .execute(merge_request)
    end

    it 'creates a merge head diff' do
      expect(subject[:status]).to eq(:success)
      expect(merge_request.reload.merge_head_diff).to be_present
    end

    context 'when merge ref head is not present' do
      before do
        allow(merge_request).to receive(:merge_ref_head).and_return(nil)
      end

      it 'returns error' do
        expect(subject[:status]).to eq(:error)
      end
    end

    context 'when failed to create merge head diff' do
      before do
        allow(merge_request).to receive(:create_merge_head_diff!).and_raise('fail')
      end

      it 'returns error' do
        expect(subject[:status]).to eq(:error)
      end
    end

    context 'when there is existing merge head diff' do
      let!(:existing_merge_head_diff) { create(:merge_request_diff, :merge_head, merge_request: merge_request) }

      it 'recreates merge head diff' do
        expect(subject[:status]).to eq(:success)
        expect(merge_request.reload.merge_head_diff).not_to eq(existing_merge_head_diff)
      end
    end
  end
end


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

module MergeRequests
  class SquashService
    include BaseServiceUtility
    include MergeRequests::ErrorLogger

    def initialize(merge_request:, current_user:, commit_message:)
      @merge_request = merge_request
      @target_project = merge_request.target_project
      @current_user = current_user
      @commit_message = commit_message
    end

    def execute
      # If performing a squash would result in no change, then
      # immediately return a success message without performing a squash
      if merge_request.commits_count == 1 && message&.strip == merge_request.first_commit.safe_message&.strip
        return success(squash_sha: merge_request.diff_head_sha)
      end

      return error(s_("MergeRequests|Squashing not allowed: This project doesn't allow you to squash commits when merging.")) if squash_forbidden?

      squash! || error(s_('MergeRequests|Squashing failed: Squash the commits locally, resolve any conflicts, then push the branch.'))
    end

    private

    attr_reader :merge_request, :target_project, :current_user, :commit_message

    def squash!
      squash_sha = repository.squash(current_user, merge_request, message)

      success(squash_sha: squash_sha)
    rescue StandardError => e
      log_error(exception: e, message: 'Failed to squash merge request')

      false
    end

    def squash_forbidden?
      target_project.squash_never?
    end

    def repository
      target_project.repository
    end

    def message
      commit_message.presence || merge_request.default_squash_commit_message(user: current_user)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::SquashService, feature_category: :source_code_management do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:user) { project.first_owner }

  let(:service) { described_class.new(merge_request: merge_request, current_user: user, commit_message: commit_message) }
  let(:commit_message) { nil }
  let(:repository) { project.repository.raw }
  let(:log_error) { "Failed to squash merge request #{merge_request.to_reference(full: true)}:" }

  let(:squash_dir_path) do
    File.join(Gitlab.config.shared.path, 'tmp/squash', repository.gl_repository, merge_request.id.to_s)
  end

  let_it_be(:merge_request_with_one_commit) do
    create(
      :merge_request,
      source_branch: 'feature', source_project: project,
      target_branch: 'master', target_project: project
    )
  end

  let_it_be(:merge_request_with_only_new_files) do
    create(
      :merge_request,
      source_branch: 'video', source_project: project,
      target_branch: 'master', target_project: project
    )
  end

  let_it_be(:merge_request_with_large_files) do
    create(
      :merge_request,
      source_branch: 'squash-large-files', source_project: project,
      target_branch: 'master', target_project: project
    )
  end

  shared_examples 'the squash succeeds' do
    it 'returns the squashed commit SHA' do
      result = service.execute

      expect(result).to match(status: :success, squash_sha: a_string_matching(/\h{40}/))
      expect(result[:squash_sha]).not_to eq(merge_request.diff_head_sha)
    end

    it 'cleans up the temporary directory' do
      service.execute

      expect(File.exist?(squash_dir_path)).to be(false)
    end

    it 'does not keep the branch push event' do
      expect { service.execute }.not_to change { Event.count }
    end

    context 'when there is a single commit in the merge request' do
      before do
        expect(merge_request).to receive(:commits_count).at_least(:once).and_return(1)
      end

      it 'will still perform the squash' do
        expect(merge_request.target_project.repository).to receive(:squash).and_return('sha')

        service.execute
      end

      context 'when squash message matches commit message' do
        let(:commit_message) { merge_request.first_commit.safe_message }

        it 'returns that commit SHA' do
          result = service.execute

          expect(result).to match(status: :success, squash_sha: merge_request.diff_head_sha)
        end

        it 'does not perform any git actions' do
          expect(repository).not_to receive(:squash)

          service.execute
        end
      end

      context 'when squash message matches commit message but without trailing new line' do
        let(:commit_message) { merge_request.first_commit.safe_message.strip }

        it 'returns that commit SHA' do
          result = service.execute

          expect(result).to match(status: :success, squash_sha: merge_request.diff_head_sha)
        end

        it 'does not perform any git actions' do
          expect(repository).not_to receive(:squash)

          service.execute
        end
      end
    end

    describe 'the squashed commit' do
      let(:squash_sha) { service.execute[:squash_sha] }
      let(:squash_commit) { project.repository.commit(squash_sha) }

      it 'copies the author info from the merge request' do
        expect(squash_commit.author_name).to eq(merge_request.author.name)
        expect(squash_commit.author_email).to eq(merge_request.author.email)
      end

      it 'sets the current user as the committer' do
        expect(squash_commit.committer_name).to eq(user.name.chomp('.'))
        expect(squash_commit.committer_email).to eq(user.email)
      end

      it 'has the same diff as the merge request, but a different SHA' do
        mr_diff = project.repository.diff(merge_request.diff_base_sha, merge_request.diff_head_sha)
        squash_diff = project.repository.diff(merge_request.diff_start_sha, squash_sha)

        expect(squash_diff.size).to eq(mr_diff.size)
        expect(squash_commit.sha).not_to eq(merge_request.diff_head_sha)
      end

      it 'has a default squash commit message if no message was provided' do
        expect(squash_commit.message.chomp).to eq(merge_request.default_squash_commit_message.chomp)
      end

      context 'if a message was provided' do
        let(:commit_message) { message }
        let(:message) { 'My custom message' }
        let(:squash_sha) { service.execute[:squash_sha] }

        it 'has the same message as the message provided' do
          expect(squash_commit.message.chomp).to eq(message)
        end
      end
    end
  end

  describe '#execute' do
    context 'when there is only one commit in the merge request' do
      let(:merge_request) { merge_request_with_one_commit }

      include_examples 'the squash succeeds'
    end

    context 'when squashing only new files' do
      let(:merge_request) { merge_request_with_only_new_files }

      include_examples 'the squash succeeds'
    end

    context 'when squashing is disabled by default on the project' do
      # Squashing is disabled by default, but it should still allow you
      # to squash-and-merge if selected through the UI
      let(:merge_request) { merge_request_with_only_new_files }

      before do
        merge_request.project.project_setting.squash_default_off!
      end

      include_examples 'the squash succeeds'
    end

    context 'when squashing is forbidden on the project' do
      let(:merge_request) { merge_request_with_only_new_files }

      before do
        merge_request.project.project_setting.squash_never!
      end

      it 'raises a squash error' do
        expect(service.execute).to match(
          status: :error,
          message: a_string_including('allow you to squash commits when merging'))
      end
    end

    context 'when squashing is enabled by default on the project' do
      let(:merge_request) { merge_request_with_only_new_files }

      before do
        merge_request.project.project_setting.squash_always!
      end

      include_examples 'the squash succeeds'
    end

    context 'when squashing with files too large to display' do
      let(:merge_request) { merge_request_with_large_files }

      include_examples 'the squash succeeds'
    end

    describe 'git errors' do
      let(:merge_request) { merge_request_with_only_new_files }
      let(:error) { 'A test error' }

      context 'with an error in Gitaly UserSquash RPC' do
        before do
          allow(repository.gitaly_operation_client).to receive(:user_squash)
            .and_raise(Gitlab::Git::Repository::GitError, error)
        end

        it 'logs the error' do
          expect(service).to receive(:log_error).with(exception: an_instance_of(Gitlab::Git::Repository::GitError), message: 'Failed to squash merge request')

          service.execute
        end

        it 'returns an error' do
          expect(service.execute).to match(status: :error, message: a_string_including('Squash'))
        end
      end
    end

    context 'when any other exception is thrown' do
      let(:merge_request) { merge_request_with_only_new_files }
      let(:merge_request_ref) { merge_request.to_reference(full: true) }
      let(:exception) { RuntimeError.new('A test error') }

      before do
        allow(merge_request.target_project.repository).to receive(:squash).and_raise(exception)
      end

      it 'logs the error' do
        expect(service).to receive(:log_error).with(exception: exception, message: 'Failed to squash merge request').and_call_original
        expect(Gitlab::ErrorTracking).to receive(:track_exception).with(exception,
          {
            class: described_class.to_s,
            merge_request: merge_request_ref,
            merge_request_id: merge_request.id,
            message: 'Failed to squash merge request',
            save_message_on_model: false
          }).and_call_original

        service.execute
      end

      it 'returns an error' do
        expect(service.execute).to match(status: :error, message: a_string_including('Squash'))
      end

      it 'cleans up the temporary directory' do
        service.execute

        expect(File.exist?(squash_dir_path)).to be(false)
      end
    end
  end
end


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

module MergeRequests
  class DeleteNonLatestDiffsService
    BATCH_SIZE = 10

    def initialize(merge_request)
      @merge_request = merge_request
    end

    def execute
      diffs = @merge_request.non_latest_diffs.with_files

      diffs.each_batch(of: BATCH_SIZE) do |relation, index|
        ids = relation.pluck_primary_key.map { |id| [id] }
        DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids) # rubocop:disable Scalability/BulkPerformWithContext
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state,
  feature_category: :code_review_workflow do
  let(:merge_request) { create(:merge_request) }

  let!(:subject) { described_class.new(merge_request) }

  describe '#execute' do
    before do
      stub_const("#{described_class.name}::BATCH_SIZE", 2)

      3.times { merge_request.create_merge_request_diff }
      merge_request.create_merge_head_diff
      merge_request.reset
    end

    it 'schedules non-latest merge request diffs removal',
      quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/426807' do
      diffs = merge_request.merge_request_diffs

      expect(diffs.count).to eq(4)

      freeze_time do
        expect(DeleteDiffFilesWorker)
          .to receive(:bulk_perform_in)
          .with(5.minutes, [[diffs.first.id], [diffs.second.id]])
        expect(DeleteDiffFilesWorker)
          .to receive(:bulk_perform_in)
          .with(10.minutes, [[diffs.third.id]])

        subject.execute
      end
    end

    it 'schedules no removal if it is already cleaned' do
      merge_request.merge_request_diffs.each(&:clean!)

      expect(DeleteDiffFilesWorker).not_to receive(:bulk_perform_in)

      subject.execute
    end

    it 'schedules no removal if it is empty' do
      merge_request.merge_request_diffs.each { |diff| diff.update!(state: :empty) }

      expect(DeleteDiffFilesWorker).not_to receive(:bulk_perform_in)

      subject.execute
    end

    it 'schedules no removal if there is no non-latest diffs' do
      # rubocop: disable Cop/DestroyAll
      merge_request
        .merge_request_diffs
        .where.not(id: merge_request.latest_merge_request_diff_id)
        .destroy_all
      # rubocop: enable Cop/DestroyAll

      expect(DeleteDiffFilesWorker).not_to receive(:bulk_perform_in)

      subject.execute
    end
  end
end


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

module MergeRequests
  class AddSpentTimeService < UpdateService
    def execute(merge_request)
      old_associations = { total_time_spent: merge_request.total_time_spent }

      merge_request.spend_time(params[:spend_time])

      merge_request_saved = merge_request.with_transaction_returning_status do
        merge_request.save
      end

      if merge_request_saved
        create_system_notes(merge_request)

        # track usage
        track_time_spend_edits(merge_request, old_associations[:total_time_spent])

        execute_hooks(merge_request, 'update', old_associations: old_associations)
      end

      merge_request
    end

    private

    def track_time_spend_edits(merge_request, old_total_time_spent)
      if old_total_time_spent != merge_request.total_time_spent
        merge_request_activity_counter.track_time_spent_changed_action(user: current_user)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::AddSpentTimeService, feature_category: :code_review_workflow do
  let_it_be(:user) { create(:user) }
  let_it_be(:project) { create(:project, :public, :repository) }
  let_it_be_with_reload(:merge_request) { create(:merge_request, :simple, :unique_branches, source_project: project) }

  let(:duration) { 1500 }
  let(:params) { { spend_time: { duration: duration, summary: 'summary', user_id: user.id } } }
  let(:service) { described_class.new(project: project, current_user: user, params: params) }

  describe '#execute' do
    before do
      project.add_developer(user)
    end

    it 'creates a new timelog with the specified duration and summary' do
      expect { service.execute(merge_request) }.to change { Timelog.count }.from(0).to(1)

      timelog = merge_request.timelogs.last

      expect(timelog).not_to be_nil
      expect(timelog.time_spent).to eq(1500)
      expect(timelog.summary).to eq('summary')
    end

    it 'creates a system note with the time added' do
      expect { service.execute(merge_request) }.to change { Note.count }.from(0).to(1)

      system_note = merge_request.notes.last

      expect(system_note).not_to be_nil
      expect(system_note.note_html).to include('added 25m of time spent')
    end

    it 'saves usage data' do
      expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
        .to receive(:track_time_spent_changed_action).once.with(user: user)

      service.execute(merge_request)
    end

    it 'is more efficient than using the full update-service' do
      other_mr = create(:merge_request, :simple, :unique_branches, source_project: project)

      update_service = ::MergeRequests::UpdateService.new(project: project, current_user: user, params: params)
      other_mr.reload

      expect { service.execute(merge_request) }
        .to issue_fewer_queries_than { update_service.execute(other_mr) }
    end

    context 'when duration is nil' do
      let(:duration) { nil }

      it 'does not create a timelog with the specified duration' do
        expect { service.execute(merge_request) }.not_to change { Timelog.count }
        expect(merge_request).not_to be_valid
      end
    end
  end
end


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

module MergeRequests
  class UpdateReviewersService < UpdateService
    def execute(merge_request)
      return merge_request unless current_user&.can?(:update_merge_request, merge_request)

      old_reviewers = merge_request.reviewers.to_a
      old_ids = old_reviewers.map(&:id)
      new_ids = new_user_ids(merge_request, update_attrs[:reviewer_ids], :reviewers)

      return merge_request if merge_request.errors.any?
      return merge_request if new_ids.size != update_attrs[:reviewer_ids].size
      return merge_request if old_ids.to_set == new_ids.to_set # no-change

      merge_request.update!(update_attrs.merge(reviewer_ids: new_ids))
      handle_reviewers_change(merge_request, old_reviewers)
      resolve_todos_for(merge_request)
      execute_reviewers_hooks(merge_request, old_reviewers)

      merge_request
    end

    private

    def reviewer_ids
      filter_sentinel_values(params.fetch(:reviewer_ids)).first(1)
    end

    def update_attrs
      @attrs ||= { updated_by: current_user, reviewer_ids: reviewer_ids }
    end

    def execute_reviewers_hooks(merge_request, old_reviewers)
      execute_hooks(
        merge_request,
        'update',
        old_associations: { reviewers: old_reviewers }
      )
    end
  end
end

MergeRequests::UpdateReviewersService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::UpdateReviewersService, feature_category: :code_review_workflow do
  include AfterNextHelpers

  let_it_be(:group) { create(:group, :public) }
  let_it_be(:project) { create(:project, :private, :repository, group: group) }
  let_it_be(:user) { create(:user) }
  let_it_be(:user2) { create(:user) }
  let_it_be(:user3) { create(:user) }

  let_it_be_with_reload(:merge_request) do
    create(
      :merge_request,
      :simple,
      :unique_branches,
      title: 'Old title',
      description: "FYI #{user2.to_reference}",
      reviewer_ids: [user3.id],
      source_project: project,
      target_project: project,
      author: create(:user)
    )
  end

  before do
    project.add_maintainer(user)
    project.add_developer(user2)
    project.add_developer(user3)
    merge_request.errors.clear
  end

  let(:service) { described_class.new(project: project, current_user: user, params: opts) }
  let(:opts) { { reviewer_ids: [user2.id] } }

  describe 'execute' do
    def set_reviewers
      service.execute(merge_request)
    end

    def find_note(starting_with)
      merge_request.notes.find do |note|
        note && note.note.start_with?(starting_with)
      end
    end

    shared_examples 'removing all reviewers' do
      it 'removes all reviewers' do
        expect(set_reviewers).to have_attributes(reviewers: be_empty, errors: be_none)
      end
    end

    context 'when the parameters are valid' do
      context 'when using sentinel values' do
        let(:opts) { { reviewer_ids: [0] } }

        it_behaves_like 'removing all reviewers'
      end

      context 'when the reviewer_ids parameter is the empty list' do
        let(:opts) { { reviewer_ids: [] } }

        it_behaves_like 'removing all reviewers'
      end

      it 'updates the MR' do
        expect { set_reviewers }
          .to change { merge_request.reload.reviewers }.from([user3]).to([user2])
          .and change(merge_request, :updated_at)
          .and change(merge_request, :updated_by).to(user)
      end

      it 'creates system note about merge_request review request' do
        set_reviewers

        note = find_note('requested review from')

        expect(note).not_to be_nil
        expect(note.note).to include "requested review from #{user2.to_reference}"
      end

      it 'creates a pending todo for new review request' do
        set_reviewers

        attributes = {
          project: project,
          author: user,
          user: user2,
          target_id: merge_request.id,
          target_type: merge_request.class.name,
          action: Todo::REVIEW_REQUESTED,
          state: :pending
        }

        expect(Todo.where(attributes).count).to eq 1
      end

      it 'sends email reviewer change notifications to old and new reviewers', :sidekiq_inline, :mailer do
        perform_enqueued_jobs do
          set_reviewers
        end

        should_email(user2)
        should_email(user3)
      end

      it 'updates open merge request counter for reviewers', :use_clean_rails_memory_store_caching do
        # Cache them to ensure the cache gets invalidated on update
        expect(user2.review_requested_open_merge_requests_count).to eq(0)
        expect(user3.review_requested_open_merge_requests_count).to eq(1)

        set_reviewers

        expect(user2.review_requested_open_merge_requests_count).to eq(1)
        expect(user3.review_requested_open_merge_requests_count).to eq(0)
      end

      it 'updates the tracking' do
        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .to receive(:track_users_review_requested)
          .with(users: [user2])

        set_reviewers
      end

      it 'tracks reviewers changed event' do
        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .to receive(:track_reviewers_changed_action).once.with(user: user)

        set_reviewers
      end

      it_behaves_like 'triggers GraphQL subscription mergeRequestReviewersUpdated' do
        let(:action) { set_reviewers }
      end

      it 'calls MergeRequest::ResolveTodosService#async_execute' do
        expect_next_instance_of(MergeRequests::ResolveTodosService, merge_request, user) do |service|
          expect(service).to receive(:async_execute)
        end

        set_reviewers
      end

      it 'executes hooks with update action' do
        expect(service).to receive(:execute_hooks)
          .with(
            merge_request,
            'update',
            old_associations: {
              reviewers: [user3]
            }
          )

        set_reviewers
      end

      context 'when reviewers did not change' do
        let(:opts) { { reviewer_ids: merge_request.reviewer_ids } }

        it_behaves_like 'does not trigger GraphQL subscription mergeRequestReviewersUpdated' do
          let(:action) { set_reviewers }
        end
      end

      it 'does not update the reviewers if they do not have access' do
        opts[:reviewer_ids] = [create(:user).id]

        expect(set_reviewers).to have_attributes(
          reviewers: [user3],
          errors: be_any
        )
      end
    end
  end
end


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

module MergeRequests
  class PushOptionsHandlerService < ::BaseProjectService
    LIMIT = 10

    attr_reader :errors, :changes, :push_options, :target_project

    def initialize(project:, current_user:, changes:, push_options:, params: {})
      super(project: project, current_user: current_user, params: params)

      @target_project = if push_options[:target_project]
                          Project.find_by_full_path(push_options[:target_project])
                        else
                          @project.default_merge_request_target
                        end

      @changes = Gitlab::ChangesList.new(changes)
      @push_options = push_options
      @errors = []
    end

    def execute
      validate_service
      return self if errors.present?

      branches.each do |branch|
        execute_for_branch(branch)
      rescue Gitlab::Access::AccessDeniedError
        errors << 'User access was denied'
      rescue StandardError => e
        Gitlab::AppLogger.error(e)
        errors << 'An unknown error occurred'
      end

      self
    end

    private

    def branches
      changes_by_branch.keys
    end

    def changes_by_branch
      @changes_by_branch ||= changes.each_with_object({}) do |changes, result|
        next unless Gitlab::Git.branch_ref?(changes[:ref])

        # Deleted branch
        next if Gitlab::Git.blank_ref?(changes[:newrev])

        # Default branch
        branch_name = Gitlab::Git.branch_name(changes[:ref])
        next if branch_name == target_project.default_branch

        result[branch_name] = changes
      end
    end

    def validate_service
      if current_user.nil?
        errors << 'User is required'
        return
      end

      unless current_user&.can?(:read_code, target_project)
        errors << 'User access was denied'
        return
      end

      unless project == target_project || project.in_fork_network_of?(target_project)
        errors << "Projects #{project.full_path} and #{target_project.full_path} are not in the same network"
      end

      unless target_project.merge_requests_enabled?
        errors << "Merge requests are not enabled for project #{target_project.full_path}"
      end

      if branches.size > LIMIT
        errors << "Too many branches pushed (#{branches.size} were pushed, limit is #{LIMIT})"
      end

      if push_options[:target] && !target_project.repository.branch_exists?(push_options[:target])
        errors << "Target branch #{target_project.full_path}:#{push_options[:target]} does not exist"
      end
    end

    # Returns a Hash of branch => MergeRequest
    def merge_requests
      @merge_requests ||= MergeRequest.from_project(target_project)
                                      .opened
                                      .from_source_branches(branches)
                                      .index_by(&:source_branch)
    end

    def execute_for_branch(branch)
      merge_request = merge_requests[branch]

      if merge_request
        update!(merge_request)
      else
        create!(branch)
      end
    end

    def create!(branch)
      unless push_options[:create]
        errors << "A merge_request.create push option is required to create a merge request for branch #{branch}"
        return
      end

      # Use BuildService to assign the standard attributes of a merge request
      merge_request = ::MergeRequests::BuildService.new(
        project: project,
        current_user: current_user,
        params: create_params(branch)
      ).execute

      unless merge_request.errors.present?
        merge_request = ::MergeRequests::CreateService.new(
          project: project,
          current_user: current_user,
          params: merge_request.attributes.merge(
            assignee_ids: merge_request.assignee_ids,
            label_ids: merge_request.label_ids
          )
        ).execute
      end

      collect_errors_from_merge_request(merge_request) unless merge_request.persisted?
    end

    def update!(merge_request)
      merge_request = ::MergeRequests::UpdateService.new(
        project: target_project,
        current_user: current_user,
        params: update_params(merge_request)
      ).execute(merge_request)

      collect_errors_from_merge_request(merge_request) unless merge_request.valid?
    end

    def base_params
      params = {
        title: push_options[:title],
        description: push_options[:description],
        draft: push_options[:draft],
        target_branch: push_options[:target],
        force_remove_source_branch: push_options[:remove_source_branch],
        label: push_options[:label],
        unlabel: push_options[:unlabel],
        assign: push_options[:assign],
        unassign: push_options[:unassign]
      }

      params.compact!

      params[:add_labels] = params.delete(:label).keys if params.has_key?(:label)
      params[:remove_labels] = params.delete(:unlabel).keys if params.has_key?(:unlabel)

      params[:add_assignee_ids] = convert_to_user_ids(params.delete(:assign).keys) if params.has_key?(:assign)
      params[:remove_assignee_ids] = convert_to_user_ids(params.delete(:unassign).keys) if params.has_key?(:unassign)

      if push_options[:milestone]
        milestone = Milestone.for_projects_and_groups(@project, @project.ancestors_upto)&.find_by_name(push_options[:milestone])
        params[:milestone_id] = milestone.id if milestone
      end

      if params.key?(:description)
        params[:description] = params[:description].gsub('\n', "\n")
      end

      params
    end

    def merge_params(branch)
      return {} unless push_options.key?(:merge_when_pipeline_succeeds)

      {
        merge_when_pipeline_succeeds: push_options[:merge_when_pipeline_succeeds],
        merge_user: current_user,
        sha: changes_by_branch.dig(branch, :newrev)
      }
    end

    def create_params(branch)
      params = base_params

      params.merge!(
        assignee_ids: [current_user.id],
        source_branch: branch,
        source_project: project,
        target_project: target_project
      )

      params.merge!(merge_params(branch))

      params[:target_branch] ||= target_project.default_branch

      params
    end

    def update_params(merge_request)
      base_params.merge(merge_params(merge_request.source_branch))
    end

    def convert_to_user_ids(ids_or_usernames)
      ids, usernames = ids_or_usernames.partition { |id_or_username| id_or_username.is_a?(Numeric) || id_or_username.match?(/\A\d+\z/) }
      ids += User.by_username(usernames).pluck(:id) unless usernames.empty? # rubocop:disable CodeReuse/ActiveRecord
      ids
    end

    def collect_errors_from_merge_request(merge_request)
      merge_request.errors.full_messages.each do |error|
        errors << error
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :source_code_management do
  include ProjectForksHelper

  let_it_be(:parent_group) { create(:group, :public) }
  let_it_be(:child_group) { create(:group, :public, parent: parent_group) }
  let_it_be(:project) { create(:project, :public, :repository, group: child_group) }
  let_it_be(:user1) { create(:user, developer_projects: [project]) }
  let_it_be(:user2) { create(:user, developer_projects: [project]) }
  let_it_be(:user3) { create(:user, developer_projects: [project]) }
  let_it_be(:forked_project) { fork_project(project, user1, repository: true) }
  let_it_be(:parent_group_milestone) { create(:milestone, group: parent_group, title: 'ParentGroupMilestone1.0') }
  let_it_be(:child_group_milestone) { create(:milestone, group: child_group, title: 'ChildGroupMilestone1.0') }
  let_it_be(:project_milestone) { create(:milestone, project: project, title: 'ProjectMilestone1.0') }

  let(:service) { described_class.new(project: project, current_user: user1, changes: changes, push_options: push_options) }
  let(:source_branch) { 'fix' }
  let(:target_branch) { 'feature' }
  let(:title) { 'my title' }
  let(:draft_title) { 'Draft: my title' }
  let(:draft) { true }
  let(:description) { 'my description' }
  let(:multiline_description) do
    <<~MD.chomp
      Line 1
      Line 2
      Line 3
    MD
  end

  let(:label1) { 'mylabel1' }
  let(:label2) { 'mylabel2' }
  let(:label3) { 'mylabel3' }
  let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
  let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
  let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
  let(:default_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{project.default_branch}" }
  let(:error_mr_required) { "A merge_request.create push option is required to create a merge request for branch #{source_branch}" }

  before do
    stub_licensed_features(multiple_merge_request_assignees: false)
  end

  shared_examples_for 'a service that can set the target of a merge request' do
    subject(:last_mr) { MergeRequest.last }

    it 'sets the target_branch' do
      service.execute

      expect(last_mr.target_branch).to eq(target_branch)
    end
  end

  shared_examples_for 'a service that can set the target project of a merge request' do
    subject(:last_mr) { MergeRequest.last }

    it 'creates a merge request with the correct target project' do
      project_path = push_options[:target_project] || project.default_merge_request_target.full_path

      expect { service.execute }.to change { MergeRequest.count }.by(1)
      expect(last_mr.target_project.full_path).to eq(project_path)
    end
  end

  shared_examples_for 'a service that can set the title of a merge request' do
    subject(:last_mr) { MergeRequest.last }

    it 'sets the title' do
      service.execute

      expect(last_mr.title).to eq(title)
    end
  end

  shared_examples_for 'a service that can set the description of a merge request' do
    subject(:last_mr) { MergeRequest.last }

    it 'sets the description' do
      service.execute

      expect(last_mr.description).to eq(description)
    end
  end

  shared_examples_for 'a service that can set the multiline description of a merge request' do
    subject(:last_mr) { MergeRequest.last }

    it 'sets the multiline description' do
      service.execute

      expect(last_mr.description).to eq(multiline_description)
    end
  end

  shared_examples_for 'a service that can set the draft of a merge request' do
    subject(:last_mr) { MergeRequest.last }

    it 'sets the draft' do
      service.execute

      expect(last_mr.draft).to eq(draft)
    end
  end

  shared_examples_for 'a service that can set the milestone of a merge request' do
    subject(:last_mr) { MergeRequest.last }

    it 'sets the milestone' do
      service.execute

      expect(last_mr.milestone&.title).to eq(expected_milestone)
    end
  end

  shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do
    subject(:last_mr) { MergeRequest.last }

    let(:change) { Gitlab::ChangesList.new(changes).changes.first }

    it 'sets auto_merge_enabled' do
      service.execute

      expect(last_mr.auto_merge_enabled).to eq(true)
      expect(last_mr.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
      expect(last_mr.merge_user).to eq(user1)
      expect(last_mr.merge_params['sha']).to eq(change[:newrev])
    end
  end

  shared_examples_for 'a service that can remove the source branch when it is merged' do
    subject(:last_mr) { MergeRequest.last }

    it 'returns true to force_remove_source_branch?' do
      service.execute

      expect(last_mr.force_remove_source_branch?).to eq(true)
    end
  end

  shared_examples_for 'a service that can change labels of a merge request' do |count|
    subject(:last_mr) { MergeRequest.last }

    it 'changes label count' do
      service.execute

      expect(last_mr.label_ids.count).to eq(count)
    end
  end

  shared_examples_for 'a service that does not update a merge request' do
    it do
      expect { service.execute }.not_to change { MergeRequest.maximum(:updated_at) }
    end
  end

  shared_examples_for 'a service that does nothing' do
    include_examples 'a service that does not create a merge request'
    include_examples 'a service that does not update a merge request'
  end

  shared_examples 'with a deleted branch' do
    let(:changes) { deleted_branch_changes }

    it_behaves_like 'a service that does nothing'
  end

  shared_examples 'with the project default branch' do
    let(:changes) { default_branch_changes }

    it_behaves_like 'a service that does nothing'
  end

  describe '`create` push option' do
    let(:push_options) { { create: true } }

    context 'with a new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that can create a merge request'
    end

    context 'with an existing branch but no open MR' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'a service that can create a merge request'
    end

    context 'with an existing branch that has a merge request open' do
      let(:changes) { existing_branch_changes }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

      it_behaves_like 'a service that does not create a merge request'
    end

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'
  end

  describe '`merge_when_pipeline_succeeds` push option' do
    let(:push_options) { { merge_when_pipeline_succeeds: true } }

    context 'with a new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, merge_when_pipeline_succeeds: true } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the merge request to merge when pipeline succeeds'
      end
    end

    context 'with an existing branch but no open MR' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, merge_when_pipeline_succeeds: true } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the merge request to merge when pipeline succeeds'
      end
    end

    context 'with an existing branch that has a merge request open' do
      let(:changes) { existing_branch_changes }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

      it_behaves_like 'a service that does not create a merge request'
      it_behaves_like 'a service that can set the merge request to merge when pipeline succeeds'
    end

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'
  end

  describe '`remove_source_branch` push option' do
    let(:push_options) { { remove_source_branch: true } }

    context 'with a new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, remove_source_branch: true } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can remove the source branch when it is merged'
      end
    end

    context 'with an existing branch but no open MR' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, remove_source_branch: true } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can remove the source branch when it is merged'
      end
    end

    context 'with an existing branch that has a merge request open' do
      let(:changes) { existing_branch_changes }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

      it_behaves_like 'a service that does not create a merge request'
      it_behaves_like 'a service that can remove the source branch when it is merged'
    end

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'
  end

  describe '`target` push option' do
    let(:push_options) { { target: target_branch } }

    context 'with a new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, target: target_branch } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the target of a merge request'
      end
    end

    context 'with an existing branch but no open MR' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, target: target_branch } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the target of a merge request'
      end
    end

    context 'with an existing branch that has a merge request open' do
      let(:changes) { existing_branch_changes }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

      it_behaves_like 'a service that does not create a merge request'
      it_behaves_like 'a service that can set the target of a merge request'
    end

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'
  end

  describe '`target_project` push option' do
    let(:changes) { new_branch_changes }
    let(:double_forked_project) { fork_project(forked_project, user1, repository: true) }
    let(:service) { described_class.new(project: double_forked_project, current_user: user1, changes: changes, push_options: push_options) }
    let(:push_options) { { create: true, target_project: target_project.full_path } }

    context 'to self' do
      let(:target_project) { double_forked_project }

      it_behaves_like 'a service that can set the target project of a merge request'
    end

    context 'to intermediate project' do
      let(:target_project) { forked_project }

      it_behaves_like 'a service that can set the target project of a merge request'
    end

    context 'to base project' do
      let(:target_project) { project }

      it_behaves_like 'a service that can set the target project of a merge request'
    end
  end

  describe '`title` push option' do
    let(:push_options) { { title: title } }

    context 'with a new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, title: title } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the title of a merge request'
      end
    end

    context 'with an existing branch but no open MR' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, title: title } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the title of a merge request'
      end
    end

    context 'with an existing branch that has a merge request open' do
      let(:changes) { existing_branch_changes }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

      it_behaves_like 'a service that does not create a merge request'
      it_behaves_like 'a service that can set the title of a merge request'
    end

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'
  end

  describe '`description` push option' do
    let(:push_options) { { description: description } }

    context 'with a new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, description: description } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the description of a merge request'
      end
    end

    context 'with an existing branch but no open MR' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, description: description } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the description of a merge request'
      end
    end

    context 'with an existing branch that has a merge request open' do
      let(:changes) { existing_branch_changes }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

      it_behaves_like 'a service that does not create a merge request'
      it_behaves_like 'a service that can set the description of a merge request'

      context 'with a multiline description' do
        let(:push_options) { { description: "Line 1\\nLine 2\\nLine 3" } }

        it_behaves_like 'a service that does not create a merge request'
        it_behaves_like 'a service that can set the multiline description of a merge request'
      end
    end

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'
  end

  describe '`draft` push option' do
    let(:push_options) { { draft: draft } }

    context 'with a new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, draft: draft } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the draft of a merge request'
      end
    end

    context 'with an existing branch but no open MR' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, draft: draft } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the draft of a merge request'
      end
    end

    context 'with an existing branch that has a merge request open' do
      let(:changes) { existing_branch_changes }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

      it_behaves_like 'a service that does not create a merge request'
      it_behaves_like 'a service that can set the draft of a merge request'
    end

    context 'draft title provided while `draft` push option is set to false' do
      let(:push_options) { { create: true, draft: false, title: draft_title } }
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that can create a merge request'
      it_behaves_like 'a service that can set the draft of a merge request'
    end

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'
  end

  describe '`label` push option' do
    let(:push_options) { { label: { label1 => 1, label2 => 1 } } }

    context 'with a new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, label: { label1 => 1, label2 => 1 } } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can change labels of a merge request', 2
      end
    end

    context 'with an existing branch but no open MR' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, label: { label1 => 1, label2 => 1 } } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can change labels of a merge request', 2
      end
    end

    context 'with an existing branch that has a merge request open' do
      let(:changes) { existing_branch_changes }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

      it_behaves_like 'a service that does not create a merge request'
      it_behaves_like 'a service that can change labels of a merge request', 2
    end

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'
  end

  describe '`unlabel` push option' do
    let(:push_options) { { label: { label1 => 1, label2 => 1 }, unlabel: { label1 => 1, label3 => 1 } } }

    context 'with a new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, label: { label1 => 1, label2 => 1 }, unlabel: { label1 => 1, label3 => 1 } } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can change labels of a merge request', 1
      end
    end

    context 'with an existing branch but no open MR' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'a service that does not create a merge request'

      it 'adds an error to the service' do
        service.execute

        expect(service.errors).to include(error_mr_required)
      end

      context 'when coupled with the `create` push option' do
        let(:push_options) { { create: true, label: { label1 => 1, label2 => 1 }, unlabel: { label1 => 1, label3 => 1 } } }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can change labels of a merge request', 1
      end
    end

    context 'with an existing branch that has a merge request open' do
      let(:changes) { existing_branch_changes }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

      it_behaves_like 'a service that does not create a merge request'
      it_behaves_like 'a service that can change labels of a merge request', 1
    end

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'
  end

  describe '`milestone` push option' do
    context 'with a valid milestone' do
      let(:expected_milestone) { project_milestone.title }
      let(:push_options) { { milestone: project_milestone.title } }

      context 'with a new branch' do
        let(:changes) { new_branch_changes }

        it_behaves_like 'a service that does not create a merge request'

        it 'adds an error to the service' do
          service.execute

          expect(service.errors).to include(error_mr_required)
        end

        context 'when coupled with the `create` push option' do
          let(:push_options) { { create: true, milestone: project_milestone.title } }

          it_behaves_like 'a service that can create a merge request'
          it_behaves_like 'a service that can set the milestone of a merge request'
        end
      end

      context 'with an existing branch but no open MR' do
        let(:changes) { existing_branch_changes }

        it_behaves_like 'a service that does not create a merge request'

        it 'adds an error to the service' do
          service.execute

          expect(service.errors).to include(error_mr_required)
        end

        context 'when coupled with the `create` push option' do
          let(:push_options) { { create: true, milestone: project_milestone.title } }

          it_behaves_like 'a service that can create a merge request'
          it_behaves_like 'a service that can set the milestone of a merge request'
        end
      end

      context 'with an existing branch that has a merge request open' do
        let(:changes) { existing_branch_changes }
        let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

        it_behaves_like 'a service that does not create a merge request'
        it_behaves_like 'a service that can set the milestone of a merge request'
      end

      it_behaves_like 'with a deleted branch'
      it_behaves_like 'with the project default branch'
    end

    context 'with invalid milestone' do
      let(:expected_milestone) { nil }
      let(:changes) { new_branch_changes }
      let(:push_options) { { create: true, milestone: 'invalid_milestone' } }

      it_behaves_like 'a service that can set the milestone of a merge request'
    end

    context 'with an ancestor milestone' do
      let(:changes) { existing_branch_changes }

      context 'with immediate parent milestone' do
        let(:push_options) { { create: true, milestone: child_group_milestone.title } }
        let(:expected_milestone) { child_group_milestone.title }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the milestone of a merge request'
      end

      context 'with multi-level ancestor milestone' do
        let(:push_options) { { create: true, milestone: parent_group_milestone.title } }
        let(:expected_milestone) { parent_group_milestone.title }

        it_behaves_like 'a service that can create a merge request'
        it_behaves_like 'a service that can set the milestone of a merge request'
      end
    end
  end

  shared_examples 'with an existing branch that has a merge request open in foss' do
    let(:changes) { existing_branch_changes }
    let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }

    it_behaves_like 'a service that does not create a merge request'
    it_behaves_like 'a service that can change assignees of a merge request', 1
  end

  describe '`assign` push option' do
    let(:assigned) { { user2.id => 1, user3.id => 1 } }
    let(:unassigned) { nil }
    let(:push_options) { { assign: assigned, unassign: unassigned } }

    it_behaves_like 'with a new branch', 1
    it_behaves_like 'with an existing branch but no open MR', 1
    it_behaves_like 'with an existing branch that has a merge request open in foss'

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'

    context 'when passing in usernames' do
      # makes sure that usernames starting with numbers aren't treated as IDs
      let(:user2) { create(:user, username: '123user', developer_projects: [project]) }
      let(:user3) { create(:user, username: '999user', developer_projects: [project]) }
      let(:assigned) { { user2.username => 1, user3.username => 1 } }

      it_behaves_like 'with an existing branch that has a merge request open in foss'
    end
  end

  describe '`unassign` push option' do
    let(:assigned) { { user2.id => 1, user3.id => 1 } }
    let(:unassigned) { { user1.id => 1, user3.id => 1 } }
    let(:push_options) { { assign: assigned, unassign: unassigned } }

    it_behaves_like 'with a new branch', 1
    it_behaves_like 'with an existing branch but no open MR', 1
    it_behaves_like 'with an existing branch that has a merge request open in foss'

    it_behaves_like 'with a deleted branch'
    it_behaves_like 'with the project default branch'

    context 'when passing in usernames' do
      let(:assigned) { { user2.username => 1, user3.username => 1 } }
      let(:unassigned) { { user1.username => 1, user3.username => 1 } }

      it_behaves_like 'with an existing branch that has a merge request open in foss'
    end
  end

  describe 'multiple pushed branches' do
    let(:push_options) { { create: true } }
    let(:changes) do
      [
        new_branch_changes,
        "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/feature_conflict"
      ]
    end

    it 'creates a merge request per branch' do
      expect { service.execute }.to change { MergeRequest.count }.by(2)
    end

    context 'when there are too many pushed branches' do
      let(:limit) { MergeRequests::PushOptionsHandlerService::LIMIT }
      let(:changes) do
        TestEnv::BRANCH_SHA.to_a[0..limit].map do |x|
          "#{Gitlab::Git::BLANK_SHA} #{x.first} refs/heads/#{x.last}"
        end
      end

      it 'records an error' do
        service.execute

        expect(service.errors).to eq(["Too many branches pushed (#{limit + 1} were pushed, limit is #{limit})"])
      end
    end
  end

  describe 'no push options' do
    let(:push_options) { {} }
    let(:changes) { new_branch_changes }

    it_behaves_like 'a service that does nothing'
  end

  describe 'no user' do
    let(:user1) { nil }
    let(:user2) { nil }
    let(:user3) { nil }
    let(:push_options) { { create: true } }
    let(:changes) { new_branch_changes }

    it 'records an error' do
      service.execute

      expect(service.errors).to eq(['User is required'])
    end
  end

  describe 'unauthorized user' do
    let(:push_options) { { create: true } }
    let(:changes) { new_branch_changes }

    it 'records an error', :sidekiq_inline do
      Members::DestroyService.new(user1).execute(ProjectMember.find_by!(user_id: user1.id))

      service.execute

      expect(service.errors).to eq(['User access was denied'])
    end
  end

  describe 'handling unexpected exceptions' do
    let(:push_options) { { create: true } }
    let(:changes) { new_branch_changes }
    let(:exception) { StandardError.new('My standard error') }

    def run_service_with_exception
      allow_next_instance_of(MergeRequests::BuildService) do |instance|
        allow(instance).to receive(:execute).and_raise(exception)
      end

      service.execute
    end

    it 'records an error' do
      run_service_with_exception

      expect(service.errors).to eq(['An unknown error occurred'])
    end

    it 'writes to Gitlab::AppLogger' do
      expect(Gitlab::AppLogger).to receive(:error).with(exception)

      run_service_with_exception
    end
  end

  describe 'when target is not a valid branch name' do
    let(:push_options) { { create: true, target: 'my-branch' } }
    let(:changes) { new_branch_changes }

    it 'records an error' do
      service.execute

      expect(service.errors).to eq(["Target branch #{project.full_path}:my-branch does not exist"])
    end
  end

  describe 'when the target project does not exist' do
    let(:push_options) { { create: true, target: 'my-branch', target_project: 'does-not-exist' } }
    let(:changes) { default_branch_changes }

    it 'records an error', :sidekiq_inline do
      service.execute

      expect(service.errors).to eq(["User access was denied"])
    end
  end

  describe 'when user does not have access to target project' do
    let(:push_options) { { create: true, target: 'my-branch' } }
    let(:changes) { default_branch_changes }

    before do
      allow(user1).to receive(:can?).with(:read_code, project).and_return(false)
    end

    it 'records an error', :sidekiq_inline do
      service.execute

      expect(service.errors).to eq(["User access was denied"])
    end
  end

  describe 'when MRs are not enabled' do
    let(:project) { create(:project, :public, :repository).tap { |pr| pr.add_developer(user1) } }
    let(:push_options) { { create: true } }
    let(:changes) { new_branch_changes }

    it 'records an error' do
      expect(project).to receive(:merge_requests_enabled?).and_return(false)

      service.execute

      expect(service.errors).to eq(["Merge requests are not enabled for project #{project.full_path}"])
    end
  end

  describe 'when projects are unrelated' do
    let(:unrelated_project) { create(:project, :public, :repository, group: child_group) }
    let(:push_options) { { create: true, target_project: unrelated_project.full_path } }
    let(:changes) { new_branch_changes }

    it 'records an error' do
      service.execute

      expect(service.errors).to eq(["Projects #{project.full_path} and #{unrelated_project.full_path} are not in the same network"])
    end
  end

  describe 'when MR has ActiveRecord errors' do
    let(:push_options) { { create: true } }
    let(:changes) { new_branch_changes }

    it 'adds the error to its errors property' do
      invalid_merge_request = MergeRequest.new
      invalid_merge_request.errors.add(:base, 'my error')

      expect_next_instance_of(MergeRequests::CreateService) do |instance|
        expect(instance).to receive(:execute).and_return(invalid_merge_request)
      end

      service.execute

      expect(service.errors).to eq(['my error'])
    end
  end
end