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

module MergeRequests
  class BaseService < ::IssuableBaseService
    extend ::Gitlab::Utils::Override
    include MergeRequests::AssignsMergeParams
    include MergeRequests::ErrorLogger

    delegate :repository, to: :project

    def initialize(project:, current_user: nil, params: {})
      super(container: project, current_user: current_user, params: params)
    end

    def create_note(merge_request, state = merge_request.state)
      SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, state, nil)
    end

    def hook_data(merge_request, action, old_rev: nil, old_associations: {})
      hook_data = merge_request.to_hook_data(current_user, old_associations: old_associations)
      hook_data[:object_attributes][:action] = action
      if old_rev && !Gitlab::Git.blank_ref?(old_rev)
        hook_data[:object_attributes][:oldrev] = old_rev
      end

      hook_data
    end

    def execute_hooks(merge_request, action = 'open', old_rev: nil, old_associations: {})
      # NOTE: Due to the async merge request diffs generation, we need to skip this for CreateService and execute it in
      #   AfterCreateService instead so that the webhook consumers receive the update when diffs are ready.
      return if merge_request.skip_ensure_merge_request_diff

      merge_data = Gitlab::Lazy.new { hook_data(merge_request, action, old_rev: old_rev, old_associations: old_associations) }
      merge_request.project.execute_hooks(merge_data, :merge_request_hooks)
      merge_request.project.execute_integrations(merge_data, :merge_request_hooks)

      execute_external_hooks(merge_request, merge_data)
      execute_group_mention_hooks(merge_request, merge_data) if action == 'open'

      enqueue_jira_connect_messages_for(merge_request)
    end

    def execute_external_hooks(merge_request, merge_data)
      # Implemented in EE
    end

    def execute_group_mention_hooks(merge_request, merge_data)
      return unless merge_request.instance_of?(MergeRequest)

      args = {
        mentionable_type: 'MergeRequest',
        mentionable_id: merge_request.id,
        hook_data: merge_data,
        is_confidential: false
      }

      merge_request.run_after_commit_or_now do
        Integrations::GroupMentionWorker.perform_async(args)
      end
    end

    def handle_changes(merge_request, options)
      old_associations = options.fetch(:old_associations, {})
      old_assignees = old_associations.fetch(:assignees, [])
      old_reviewers = old_associations.fetch(:reviewers, [])

      handle_assignees_change(merge_request, old_assignees) if merge_request.assignees != old_assignees
      handle_reviewers_change(merge_request, old_reviewers) if merge_request.reviewers != old_reviewers
    end

    def handle_assignees_change(merge_request, old_assignees)
      MergeRequests::HandleAssigneesChangeService
        .new(project: project, current_user: current_user)
        .async_execute(merge_request, old_assignees)
    end

    def handle_reviewers_change(merge_request, old_reviewers)
      affected_reviewers = (old_reviewers + merge_request.reviewers) - (old_reviewers & merge_request.reviewers)
      create_reviewer_note(merge_request, old_reviewers)
      notification_service.async.changed_reviewer_of_merge_request(merge_request, current_user, old_reviewers)
      todo_service.reassigned_reviewable(merge_request, current_user, old_reviewers)
      invalidate_cache_counts(merge_request, users: affected_reviewers.compact)

      new_reviewers = merge_request.reviewers - old_reviewers
      merge_request_activity_counter.track_users_review_requested(users: new_reviewers)
      merge_request_activity_counter.track_reviewers_changed_action(user: current_user)
      trigger_merge_request_reviewers_updated(merge_request)

      capture_suggested_reviewers_accepted(merge_request)
    end

    def cleanup_environments(merge_request)
      Environments::StopService.new(merge_request.source_project, current_user)
                               .execute_for_merge_request_pipeline(merge_request)
    end

    def cancel_review_app_jobs!(merge_request)
      environments = merge_request.environments_in_head_pipeline.in_review_folder.available
      environments.each { |environment| environment.cancel_deployment_jobs! }
    end

    def source_project
      @source_project ||= merge_request.source_project
    end

    def target_project
      @target_project ||= merge_request.target_project
    end

    # Don't try to print expensive instance variables.
    def inspect
      return "#<#{self.class}>" unless respond_to?(:merge_request) && merge_request

      "#<#{self.class} #{merge_request.to_reference(full: true)}>"
    end

    def merge_request_activity_counter
      Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter
    end

    def deactivate_pages_deployments(merge_request)
      Pages::DeactivateMrDeploymentsWorker.perform_async(merge_request)
    end

    private

    def self.constructor_container_arg(value)
      { project: value }
    end

    def refresh_pipelines_on_merge_requests(merge_request, allow_duplicate: false)
      create_pipeline_for(merge_request, current_user, async: true, allow_duplicate: allow_duplicate)
    end

    def enqueue_jira_connect_messages_for(merge_request)
      return unless project.jira_subscription_exists?

      if Atlassian::JiraIssueKeyExtractor.has_keys?(merge_request.title, merge_request.description)
        JiraConnect::SyncMergeRequestWorker.perform_async(merge_request.id, Atlassian::JiraConnect::Client.generate_update_sequence_id)
      end
    end

    def create(merge_request)
      self.params = assign_allowed_merge_params(merge_request, params)

      super
    end

    def update(merge_request)
      self.params = assign_allowed_merge_params(merge_request, params)

      super
    end

    override :handle_quick_actions
    def handle_quick_actions(merge_request)
      super
      handle_draft_event(merge_request)
    end

    def handle_draft_event(merge_request)
      if draft_event = params.delete(:wip_event)
        # We update the title that is provided in the params or we use the mr title
        title = params[:title] || merge_request.title
        params[:title] = case draft_event
                         when 'draft' then MergeRequest.draft_title(title)
                         when 'ready' then MergeRequest.draftless_title(title)
                         end
      end
    end

    def filter_params(merge_request)
      super

      unless merge_request.can_allow_collaboration?(current_user)
        params.delete(:allow_collaboration)
      end

      filter_reviewer(merge_request)
      filter_suggested_reviewers
    end

    def filter_reviewer(merge_request)
      return if params[:reviewer_ids].blank?

      unless can_admin_issuable?(merge_request)
        params.delete(:reviewer_ids)

        return
      end

      unless merge_request.allows_multiple_reviewers?
        params[:reviewer_ids] = params[:reviewer_ids].first(1)
      end

      reviewer_ids = params[:reviewer_ids].select { |reviewer_id| user_can_read?(merge_request, reviewer_id) }

      if params[:reviewer_ids].map(&:to_s) == [IssuableFinder::Params::NONE]
        params[:reviewer_ids] = []
      elsif reviewer_ids.any?
        params[:reviewer_ids] = reviewer_ids
      else
        params.delete(:reviewer_ids)
      end
    end

    def filter_suggested_reviewers
      # Implemented in EE
    end

    def merge_request_metrics_service(merge_request)
      MergeRequestMetricsService.new(merge_request.metrics)
    end

    def create_assignee_note(merge_request, old_assignees)
      SystemNoteService.change_issuable_assignees(
        merge_request, merge_request.project, current_user, old_assignees)
    end

    def create_reviewer_note(merge_request, old_reviewers)
      SystemNoteService.change_issuable_reviewers(
        merge_request, merge_request.project, current_user, old_reviewers)
    end

    def create_pipeline_for(merge_request, user, async: false, allow_duplicate: false)
      create_pipeline_params = params.slice(:push_options).merge(allow_duplicate: allow_duplicate)

      if async
        MergeRequests::CreatePipelineWorker.perform_async(
          project.id,
          user.id,
          merge_request.id,
          create_pipeline_params.deep_stringify_keys)
      else
        MergeRequests::CreatePipelineService
          .new(project: project, current_user: user, params: create_pipeline_params)
          .execute(merge_request)
      end
    end

    def abort_auto_merge(merge_request, reason)
      AutoMergeService.new(project, current_user).abort(merge_request, reason)
    end

    # Returns all origin and fork merge requests from `@project` satisfying passed arguments.
    # rubocop: disable CodeReuse/ActiveRecord
    def merge_requests_for(source_branch, mr_states: [:opened])
      @project.source_of_merge_requests
        .with_state(mr_states)
        .where(source_branch: source_branch)
        .preload(:source_project) # we don't need #includes since we're just preloading for the #select
        .select(&:source_project)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def pipeline_merge_requests(pipeline)
      pipeline.all_merge_requests.opened.each do |merge_request|
        next unless pipeline.id == merge_request.head_pipeline_id

        yield merge_request
      end
    end

    def trigger_merge_request_reviewers_updated(merge_request)
      GraphqlTriggers.merge_request_reviewers_updated(merge_request)
    end

    def trigger_merge_request_merge_status_updated(merge_request)
      GraphqlTriggers.merge_request_merge_status_updated(merge_request)
    end

    def trigger_merge_request_approval_state_updated(merge_request)
      GraphqlTriggers.merge_request_approval_state_updated(merge_request)
    end

    def capture_suggested_reviewers_accepted(merge_request)
      # Implemented in EE
    end
  end
end

MergeRequests::BaseService.prepend_mod_with('MergeRequests::BaseService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

module MergeRequests
  class ExampleService < MergeRequests::BaseService
    def execute(merge_request, async: false, allow_duplicate: false)
      create_pipeline_for(merge_request, current_user, async: async, allow_duplicate: allow_duplicate)
    end
  end
end

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

  let_it_be(:project) { create(:project, :repository) }

  let(:user) { project.first_owner }
  let(:title) { 'Awesome merge_request' }
  let(:params) do
    {
      title: title,
      description: 'please fix',
      source_branch: 'feature',
      target_branch: 'master'
    }
  end

  describe '#execute_hooks' do
    subject { MergeRequests::CreateService.new(project: project, current_user: user, params: params).execute }

    shared_examples 'enqueues Jira sync worker' do
      specify :aggregate_failures do
        expect(JiraConnect::SyncMergeRequestWorker).to receive(:perform_async).with(kind_of(Numeric), kind_of(Numeric)).and_call_original
        Sidekiq::Testing.fake! do
          expect { subject }.to change(JiraConnect::SyncMergeRequestWorker.jobs, :size).by(1)
        end
      end
    end

    shared_examples 'does not enqueue Jira sync worker' do
      it do
        Sidekiq::Testing.fake! do
          expect { subject }.not_to change(JiraConnect::SyncMergeRequestWorker.jobs, :size)
        end
      end
    end

    context 'with a Jira subscription' do
      before do
        create(:jira_connect_subscription, namespace: project.namespace)
      end

      context 'MR contains Jira issue key' do
        let(:title) { 'Awesome merge_request with issue JIRA-123' }

        it_behaves_like 'does not enqueue Jira sync worker'

        context 'for UpdateService' do
          subject { MergeRequests::UpdateService.new(project: project, current_user: user, params: params).execute(merge_request) }

          let(:merge_request) do
            create(:merge_request, :simple, title: 'Old title',
              assignee_ids: [user.id],
              source_project: project,
              author: user)
          end

          it_behaves_like 'enqueues Jira sync worker'
        end
      end

      context 'MR does not contain Jira issue key' do
        it_behaves_like 'does not enqueue Jira sync worker'
      end
    end

    context 'without a Jira subscription' do
      it_behaves_like 'does not enqueue Jira sync worker'
    end
  end

  describe `#create_pipeline_for` do
    let_it_be(:merge_request) { create(:merge_request) }

    subject { MergeRequests::ExampleService.new(project: project, current_user: user, params: params) }

    context 'async: false' do
      it 'creates a pipeline directly' do
        expect(MergeRequests::CreatePipelineService)
          .to receive(:new)
          .with(hash_including(project: project, current_user: user, params: { allow_duplicate: false }))
          .and_call_original
        expect(MergeRequests::CreatePipelineWorker).not_to receive(:perform_async)

        subject.execute(merge_request, async: false)
      end

      context 'allow_duplicate: true' do
        it 'passes :allow_duplicate as true' do
          expect(MergeRequests::CreatePipelineService)
          .to receive(:new)
          .with(hash_including(project: project, current_user: user, params: { allow_duplicate: true }))
          .and_call_original
          expect(MergeRequests::CreatePipelineWorker).not_to receive(:perform_async)

          subject.execute(merge_request, async: false, allow_duplicate: true)
        end
      end
    end

    context 'async: true' do
      it 'enques a CreatePipelineWorker' do
        expect(MergeRequests::CreatePipelineService).not_to receive(:new)
        expect(MergeRequests::CreatePipelineWorker)
          .to receive(:perform_async)
          .with(project.id, user.id, merge_request.id, { "allow_duplicate" => false })
          .and_call_original

        Sidekiq::Testing.fake! do
          expect { subject.execute(merge_request, async: true) }.to change(MergeRequests::CreatePipelineWorker.jobs, :size).by(1)
        end
      end

      context 'allow_duplicate: true' do
        it 'passes :allow_duplicate as true' do
          expect(MergeRequests::CreatePipelineService).not_to receive(:new)
          expect(MergeRequests::CreatePipelineWorker)
            .to receive(:perform_async)
            .with(project.id, user.id, merge_request.id, { "allow_duplicate" => true })
            .and_call_original

          Sidekiq::Testing.fake! do
            expect { subject.execute(merge_request, async: true, allow_duplicate: true) }.to change(MergeRequests::CreatePipelineWorker.jobs, :size).by(1)
          end
        end
      end
    end
  end

  describe '#constructor_container_arg' do
    it { expect(described_class.constructor_container_arg("some-value")).to eq({ project: "some-value" }) }
  end

  describe '#inspect' do
    context 'when #merge_request is defined' do
      let(:klass) do
        Class.new(described_class) do
          def merge_request
            params[:merge_request]
          end
        end
      end

      let(:params) { {} }

      subject do
        klass
          .new(project: nil, current_user: nil, params: params)
          .inspect
      end

      it { is_expected.to eq "#<#{klass}>" }

      context 'when merge request is present' do
        let(:params) { { merge_request: build(:merge_request) } }

        it { is_expected.to eq "#<#{klass} #{params[:merge_request].to_reference(full: true)}>" }
      end
    end
  end
end


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

module MergeRequests
  class RequestReviewService < MergeRequests::BaseService
    def execute(merge_request, user)
      return error("Invalid permissions") unless can?(current_user, :update_merge_request, merge_request)

      reviewer = merge_request.find_reviewer(user)

      if reviewer
        return error("Failed to update reviewer") unless reviewer.update(state: :unreviewed)

        notify_reviewer(merge_request, user)
        trigger_merge_request_reviewers_updated(merge_request)

        success
      else
        error("Reviewer not found")
      end
    end

    private

    def notify_reviewer(merge_request, reviewer)
      notification_service.async.review_requested_of_merge_request(merge_request, current_user, reviewer)
      todo_service.create_request_review_todo(merge_request, current_user, reviewer)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::RequestReviewService, feature_category: :code_review_workflow do
  let(:current_user) { create(:user) }
  let(:user) { create(:user) }
  let(:merge_request) { create(:merge_request, reviewers: [user]) }
  let(:reviewer) { merge_request.find_reviewer(user) }
  let(:project) { merge_request.project }
  let(:service) { described_class.new(project: project, current_user: current_user) }
  let(:result) { service.execute(merge_request, user) }
  let(:todo_service) { spy('todo service') }
  let(:notification_service) { spy('notification service') }

  before do
    allow(NotificationService).to receive(:new) { notification_service }
    allow(service).to receive(:todo_service).and_return(todo_service)
    allow(service).to receive(:notification_service).and_return(notification_service)

    reviewer.update!(state: MergeRequestReviewer.states[:reviewed])

    project.add_developer(current_user)
    project.add_developer(user)
  end

  describe '#execute' do
    shared_examples_for 'failed service execution' do
      it 'returns an error' do
        expect(result[:status]).to eq :error
      end

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

    describe 'invalid permissions' do
      let(:service) { described_class.new(project: project, current_user: create(:user)) }

      it_behaves_like 'failed service execution'
    end

    describe 'reviewer does not exist' do
      let(:result) { service.execute(merge_request, create(:user)) }

      it_behaves_like 'failed service execution'
    end

    describe 'reviewer exists' do
      it 'returns success' do
        expect(result[:status]).to eq :success
      end

      it 'updates reviewers state' do
        service.execute(merge_request, user)
        reviewer.reload

        expect(reviewer.state).to eq 'unreviewed'
      end

      it 'sends email to reviewer' do
        expect(notification_service).to receive_message_chain(:async, :review_requested_of_merge_request).with(merge_request, current_user, user)

        service.execute(merge_request, user)
      end

      it 'creates a new todo for the reviewer' do
        expect(todo_service).to receive(:create_request_review_todo).with(merge_request, current_user, user)

        service.execute(merge_request, user)
      end

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


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

module MergeRequests
  class GetUrlsService < BaseProjectService
    def execute(changes)
      return [] unless project&.printing_merge_request_link_enabled

      branches = get_branches(changes)
      merge_requests_map = opened_merge_requests_from_source_branches(branches)
      branches.map do |branch|
        existing_merge_request = merge_requests_map[branch]
        if existing_merge_request
          url_for_existing_merge_request(existing_merge_request)
        else
          url_for_new_merge_request(branch)
        end
      end
    end

    private

    def opened_merge_requests_from_source_branches(branches)
      merge_requests = MergeRequest.from_project(project).opened.from_source_branches(branches)
      merge_requests.index_by(&:source_branch)
    end

    def get_branches(changes)
      return [] if project.empty_repo?
      return [] unless project.merge_requests_enabled?

      changes_list = Gitlab::ChangesList.new(changes)
      changes_list.map do |change|
        next unless Gitlab::Git.branch_ref?(change[:ref])

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

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

        branch_name
      end.compact
    end

    def url_for_new_merge_request(branch_name)
      merge_request_params = { source_branch: branch_name }
      url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params)
      { branch_name: branch_name, url: url, new_merge_request: true }
    end

    def url_for_existing_merge_request(merge_request)
      target_project = merge_request.target_project
      url = Gitlab::Routing.url_helpers.project_merge_request_url(target_project, merge_request)
      { branch_name: merge_request.source_branch, url: url, new_merge_request: false }
    end
  end
end

```

Response:
# frozen_string_literal: true

require "spec_helper"

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

  let(:project) { create(:project, :public, :repository) }
  let(:service) { described_class.new(project: project) }
  let(:source_branch) { "merge-test" }
  let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
  let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/#{merge_request.iid}" }
  let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
  let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
  let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
  let(:default_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master" }

  describe "#execute" do
    shared_examples 'new_merge_request_link' do
      it 'returns url to create new merge request' do
        result = service.execute(changes)
        expect(result).to match([{
          branch_name: source_branch,
          url: new_merge_request_url,
          new_merge_request: true
        }])
      end
    end

    shared_examples 'show_merge_request_url' do
      it 'returns url to view merge request' do
        result = service.execute(changes)
        expect(result).to match([{
          branch_name: source_branch,
          url: show_merge_request_url,
          new_merge_request: false
        }])
      end
    end

    shared_examples 'no_merge_request_url' do
      it 'returns no URL' do
        result = service.execute(changes)
        expect(result).to be_empty
      end
    end

    context 'when project is nil' do
      let(:project) { nil }
      let(:changes) { default_branch_changes }

      it_behaves_like 'no_merge_request_url'
    end

    context 'pushing to default branch' do
      let(:changes) { default_branch_changes }

      it_behaves_like 'no_merge_request_url'
    end

    context 'pushing to project with MRs disabled' do
      let(:changes) { new_branch_changes }

      before do
        project.project_feature.update_attribute(:merge_requests_access_level, ProjectFeature::DISABLED)
      end

      it_behaves_like 'no_merge_request_url'
    end

    context 'pushing one completely new branch' do
      let(:changes) { new_branch_changes }

      it_behaves_like 'new_merge_request_link'
    end

    context 'pushing to existing branch but no merge request' do
      let(:changes) { existing_branch_changes }

      it_behaves_like 'new_merge_request_link'
    end

    context 'pushing to deleted branch' do
      let(:changes) { deleted_branch_changes }

      it_behaves_like 'no_merge_request_url'
    end

    context 'pushing to existing branch and merge request opened' do
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }
      let(:changes) { existing_branch_changes }

      it_behaves_like 'show_merge_request_url'
    end

    context 'pushing to existing branch and merge request is reopened' do
      let!(:merge_request) { create(:merge_request, :opened, source_project: project, source_branch: source_branch) }
      let(:changes) { existing_branch_changes }

      it_behaves_like 'show_merge_request_url'
    end

    context 'pushing to existing branch from forked project' do
      let(:user) { create(:user) }
      let!(:forked_project) { fork_project(project, user, repository: true) }
      let!(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project, source_branch: source_branch) }
      let(:changes) { existing_branch_changes }
      # Source project is now the forked one
      let(:service) { described_class.new(project: forked_project) }

      before do
        allow(forked_project).to receive(:empty_repo?).and_return(false)
      end

      it_behaves_like 'show_merge_request_url'
    end

    context 'pushing to existing branch and merge request is closed' do
      let!(:merge_request) { create(:merge_request, :closed, source_project: project, source_branch: source_branch) }
      let(:changes) { existing_branch_changes }

      it_behaves_like 'new_merge_request_link'
    end

    context 'pushing to existing branch and merge request is merged' do
      let!(:merge_request) { create(:merge_request, :merged, source_project: project, source_branch: source_branch) }
      let(:changes) { existing_branch_changes }

      it_behaves_like 'new_merge_request_link'
    end

    context 'pushing new branch and existing branch (with merge request created) at once' do
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: "markdown") }
      let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" }
      let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/markdown" }
      let(:changes) { "#{new_branch_changes}\n#{existing_branch_changes}" }
      let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" }

      it 'returns 2 urls for both creating new and showing merge request' do
        result = service.execute(changes)
        expect(result).to match([{
          branch_name: "new_branch",
          url: new_merge_request_url,
          new_merge_request: true
        }, {
          branch_name: "markdown",
          url: show_merge_request_url,
          new_merge_request: false
        }])
      end
    end

    context 'when printing_merge_request_link_enabled is false' do
      it 'returns empty array' do
        project.update!(printing_merge_request_link_enabled: false)

        result = service.execute(existing_branch_changes)

        expect(result).to eq([])
      end
    end
  end
end


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

module MergeRequests
  # PostMergeService class
  #
  # Mark existing merge request as merged
  # and execute all hooks and notifications
  #
  class PostMergeService < MergeRequests::BaseService
    include RemovesRefs

    MAX_RETARGET_MERGE_REQUESTS = 4

    def execute(merge_request)
      return if merge_request.merged?

      # Mark the merge request as merged, everything that happens afterwards is
      # executed once
      merge_request.mark_as_merged

      create_event(merge_request)
      todo_service.merge_merge_request(merge_request, current_user)

      merge_request_activity_counter.track_merge_mr_action(user: current_user)

      create_note(merge_request)
      close_issues(merge_request)
      notification_service.merge_mr(merge_request, current_user)
      invalidate_cache_counts(merge_request, users: merge_request.assignees | merge_request.reviewers)
      merge_request.update_project_counter_caches
      delete_non_latest_diffs(merge_request)
      cancel_review_app_jobs!(merge_request)
      cleanup_environments(merge_request)
      cleanup_refs(merge_request)
      deactivate_pages_deployments(merge_request)

      execute_hooks(merge_request, 'merge')
    end

    private

    def close_issues(merge_request)
      return unless merge_request.target_branch == project.default_branch

      closed_issues = merge_request.visible_closing_issues_for(current_user)

      closed_issues.each do |issue|
        # We are intentionally only closing Issues asynchronously (excluding ExternalIssues)
        # as the worker only supports finding an Issue. We are also only experiencing
        # SQL timeouts when closing an Issue.
        if issue.is_a?(Issue)
          MergeRequests::CloseIssueWorker.perform_async(
            project.id,
            current_user.id,
            issue.id,
            merge_request.id
          )
        else
          Issues::CloseService.new(container: project, current_user: current_user).execute(issue, commit: merge_request)
        end
      end
    end

    def delete_non_latest_diffs(merge_request)
      DeleteNonLatestDiffsService.new(merge_request).execute
    end

    def create_merge_event(merge_request, current_user)
      EventCreateService.new.merge_mr(merge_request, current_user)
    end

    def create_event(merge_request)
      # Making sure MergeRequest::Metrics updates are in sync with
      # Event creation.
      Event.transaction do
        merge_event = create_merge_event(merge_request, current_user)
        merge_request_metrics_service(merge_request).merge(merge_event)
      end
    end
  end
end

MergeRequests::PostMergeService.prepend_mod_with('MergeRequests::PostMergeService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

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

  let_it_be(:user) { create(:user) }
  let_it_be(:merge_request, reload: true) { create(:merge_request, assignees: [user]) }
  let_it_be(:project) { merge_request.project }

  subject { described_class.new(project: project, current_user: user).execute(merge_request) }

  before do
    project.add_maintainer(user)
  end

  describe '#execute' do
    it_behaves_like 'cache counters invalidator'
    it_behaves_like 'merge request reviewers cache counters invalidator'

    it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do
      # Cache the counter before the MR changed state.
      project.open_merge_requests_count

      expect do
        subject

        BatchLoader::Executor.clear_current
      end.to change { project.open_merge_requests_count }.from(1).to(0)
    end

    it 'updates metrics' do
      metrics = merge_request.metrics
      metrics_service = double(MergeRequestMetricsService)
      allow(MergeRequestMetricsService)
        .to receive(:new)
        .with(metrics)
        .and_return(metrics_service)

      expect(metrics_service).to receive(:merge)

      subject
    end

    it 'calls the merge request activity counter' do
      expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
        .to receive(:track_merge_mr_action)
        .with(user: user)

      subject
    end

    it 'deletes non-latest diffs' do
      diff_removal_service = instance_double(MergeRequests::DeleteNonLatestDiffsService, execute: nil)

      expect(MergeRequests::DeleteNonLatestDiffsService)
        .to receive(:new).with(merge_request)
        .and_return(diff_removal_service)

      subject

      expect(diff_removal_service).to have_received(:execute)
    end

    it 'clean up environments for the merge request' do
      expect_next_instance_of(::Environments::StopService) do |stop_environment_service|
        expect(stop_environment_service).to receive(:execute_for_merge_request_pipeline).with(merge_request)
      end

      subject
    end

    it 'schedules CleanupRefsService' do
      expect(MergeRequests::CleanupRefsService).to receive(:schedule).with(merge_request)

      subject
    end

    context 'when there are issues to be closed' do
      let_it_be(:issue) { create(:issue, project: project) }

      before do
        merge_request.update!(target_branch: 'foo')

        allow(project).to receive(:default_branch).and_return('foo')
        allow(merge_request).to receive(:visible_closing_issues_for).and_return([issue])
      end

      it 'performs MergeRequests::CloseIssueWorker asynchronously' do
        expect(MergeRequests::CloseIssueWorker)
          .to receive(:perform_async)
          .with(project.id, user.id, issue.id, merge_request.id)

        subject

        expect(merge_request.reload).to be_merged
      end

      context 'when issue is an external issue' do
        let_it_be(:issue) { ExternalIssue.new('JIRA-123', project) }

        it 'executes Issues::CloseService' do
          expect_next_instance_of(Issues::CloseService) do |close_service|
            expect(close_service).to receive(:execute).with(issue, commit: merge_request)
          end

          subject

          expect(merge_request.reload).to be_merged
        end
      end
    end

    context 'when the merge request has review apps' do
      it 'cancels all review app deployments' do
        pipeline = create(:ci_pipeline,
          source: :merge_request_event,
          merge_request: merge_request,
          project: project,
          sha: merge_request.diff_head_sha,
          merge_requests_as_head_pipeline: [merge_request])

        review_env_a = create(:environment, project: project, state: :available, name: 'review/a')
        review_env_b = create(:environment, project: project, state: :available, name: 'review/b')
        review_env_c = create(:environment, project: project, state: :stopped, name: 'review/c')
        deploy_env = create(:environment, project: project, state: :available, name: 'deploy')

        review_job_a1 = create(:ci_build, :with_deployment, :start_review_app,
          pipeline: pipeline, project: project, environment: review_env_a.name)
        review_job_a2 = create(:ci_build, :with_deployment, :start_review_app,
          pipeline: pipeline, project: project, environment: review_env_a.name)
        finished_review_job_a = create(:ci_build, :with_deployment, :start_review_app,
          pipeline: pipeline, project: project, status: :success, environment: review_env_a.name)
        review_job_b1 = create(:ci_build, :with_deployment, :start_review_app,
          pipeline: pipeline, project: project, environment: review_env_b.name)
        review_job_b2 = create(:ci_build, :start_review_app,
          pipeline: pipeline, project: project, environment: review_env_b.name)
        review_job_c1 = create(:ci_build, :with_deployment, :start_review_app,
          pipeline: pipeline, project: project, environment: review_env_c.name)
        deploy_job = create(:ci_build, :with_deployment, :deploy_to_production,
          pipeline: pipeline, project: project, environment: deploy_env.name)

        subject

        expect(review_job_a1.reload.canceled?).to be true
        expect(review_job_a2.reload.canceled?).to be true
        expect(finished_review_job_a.reload.status).to eq "success"
        expect(finished_review_job_a.reload.canceled?).to be false
        expect(review_job_b1.reload.canceled?).to be true
        expect(review_job_b2.reload.canceled?).to be false
        expect(review_job_c1.reload.canceled?).to be false
        expect(deploy_job.reload.canceled?).to be false
      end
    end

    context 'when the merge request has a pages deployment' do
      it 'performs Pages::DeactivateMrDeploymentWorker asynchronously' do
        expect(Pages::DeactivateMrDeploymentsWorker)
          .to receive(:perform_async)
          .with(merge_request)

        subject

        expect(merge_request.reload).to be_merged
      end
    end
  end
end


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

module MergeRequests
  class ResolvedDiscussionNotificationService < MergeRequests::BaseService
    def execute(merge_request)
      return unless merge_request.discussions_resolved?

      SystemNoteService.resolve_all_discussions(merge_request, project, current_user)
      execute_hooks(merge_request, 'update')
      notification_service.async.resolve_all_discussions(merge_request, current_user)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

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

  subject { described_class.new(project: project, current_user: user) }

  describe "#execute" do
    context "when not all discussions are resolved" do
      before do
        allow(merge_request).to receive(:discussions_resolved?).and_return(false)
      end

      it "doesn't add a system note" do
        expect(SystemNoteService).not_to receive(:resolve_all_discussions)

        subject.execute(merge_request)
      end

      it "doesn't send a notification email" do
        expect_any_instance_of(NotificationService).not_to receive(:resolve_all_discussions)

        subject.execute(merge_request)
      end

      it "doesn't send a webhook" do
        expect_any_instance_of(MergeRequests::BaseService).not_to receive(:execute_hooks)

        subject.execute(merge_request)
      end
    end

    context "when all discussions are resolved" do
      before do
        allow(merge_request).to receive(:discussions_resolved?).and_return(true)
      end

      it "adds a system note" do
        expect(SystemNoteService).to receive(:resolve_all_discussions).with(merge_request, project, user)

        subject.execute(merge_request)
      end

      it "sends a notification email", :sidekiq_might_not_need_inline do
        expect_any_instance_of(NotificationService).to receive(:resolve_all_discussions).with(merge_request, user)

        subject.execute(merge_request)
      end

      it "sends a webhook" do
        expect_any_instance_of(MergeRequests::BaseService).to receive(:execute_hooks).with(merge_request, 'update')

        subject.execute(merge_request)
      end
    end
  end
end


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

module MergeRequests
  class RemoveApprovalService < MergeRequests::BaseService
    # rubocop: disable CodeReuse/ActiveRecord
    def execute(merge_request)
      return unless merge_request.approved_by?(current_user)

      # paranoid protection against running wrong deletes
      return unless merge_request.id && current_user.id

      approval = merge_request.approvals.where(user: current_user)

      trigger_approval_hooks(merge_request) do
        next unless approval.destroy_all # rubocop: disable Cop/DestroyAll

        reset_approvals_cache(merge_request)
        create_note(merge_request)
        merge_request_activity_counter.track_unapprove_mr_action(user: current_user)
        trigger_merge_request_merge_status_updated(merge_request)
        trigger_merge_request_reviewers_updated(merge_request)
        trigger_merge_request_approval_state_updated(merge_request)
      end

      success
    end
    # rubocop: enable CodeReuse/ActiveRecord

    private

    def reset_approvals_cache(merge_request)
      merge_request.approvals.reset
    end

    def trigger_approval_hooks(merge_request)
      yield

      notification_service.async.unapprove_mr(merge_request, current_user)
      execute_hooks(merge_request, 'unapproved')
    end

    def create_note(merge_request)
      SystemNoteService.unapprove_mr(merge_request, current_user)
    end
  end
end

MergeRequests::RemoveApprovalService.prepend_mod_with('MergeRequests::RemoveApprovalService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::RemoveApprovalService, feature_category: :code_review_workflow do
  describe '#execute' do
    let(:user) { create(:user) }
    let(:project) { create(:project) }
    let(:merge_request) { create(:merge_request, source_project: project) }
    let!(:existing_approval) { create(:approval, merge_request: merge_request) }

    subject(:service) { described_class.new(project: project, current_user: user) }

    def execute!
      service.execute(merge_request)
    end

    before do
      project.add_developer(user)
    end

    context 'with a user who has approved' do
      let!(:approval) { create(:approval, user: user, merge_request: merge_request) }
      let(:notification_service) { NotificationService.new }

      before do
        allow(service).to receive(:notification_service).and_return(notification_service)
      end

      it 'removes the approval' do
        expect { execute! }.to change { merge_request.approvals.size }.from(2).to(1)
      end

      it 'creates an unapproval note, triggers a web hook, and sends a notification' do
        expect(service).to receive(:execute_hooks).with(merge_request, 'unapproved')
        expect(SystemNoteService).to receive(:unapprove_mr)
        expect(notification_service).to receive_message_chain(:async, :unapprove_mr).with(merge_request, user)

        execute!
      end

      it 'tracks merge request unapprove action' do
        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .to receive(:track_unapprove_mr_action).with(user: user)

        execute!
      end

      it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
        let(:action) { execute! }
      end

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

      it_behaves_like 'triggers GraphQL subscription mergeRequestApprovalStateUpdated' do
        let(:action) { execute! }
      end
    end

    context 'with a user who has not approved' do
      it 'does not create an unapproval note and triggers web hook' do
        expect(service).not_to receive(:execute_hooks)
        expect(SystemNoteService).not_to receive(:unapprove_mr)

        execute!
      end

      it 'does not track merge request unapprove action' do
        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .not_to receive(:track_unapprove_mr_action).with(user: user)

        execute!
      end

      it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
        let(:action) { execute! }
      end

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

      it_behaves_like 'does not trigger GraphQL subscription mergeRequestApprovalStateUpdated' do
        let(:action) { execute! }
      end
    end
  end
end


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

module MergeRequests
  class ApprovalService < MergeRequests::BaseService
    def execute(merge_request)
      return unless eligible_for_approval?(merge_request)

      approval = merge_request.approvals.new(
        user: current_user,
        patch_id_sha: merge_request.current_patch_id_sha
      )

      return success unless save_approval(approval)

      reset_approvals_cache(merge_request)

      merge_request_activity_counter.track_approve_mr_action(user: current_user, merge_request: merge_request)

      trigger_merge_request_merge_status_updated(merge_request)
      trigger_merge_request_reviewers_updated(merge_request)
      trigger_merge_request_approval_state_updated(merge_request)

      # Approval side effects (things not required to be done immediately but
      # should happen after a successful approval) should be done asynchronously
      # utilizing the `Gitlab::EventStore`.
      #
      # Workers can subscribe to the `MergeRequests::ApprovedEvent`.
      Gitlab::EventStore.publish(
        MergeRequests::ApprovedEvent.new(
          data: { current_user_id: current_user.id, merge_request_id: merge_request.id }
        )
      )

      success
    end

    private

    def eligible_for_approval?(merge_request)
      merge_request.eligible_for_approval_by?(current_user)
    end

    def save_approval(approval)
      Approval.safe_ensure_unique do
        approval.save
      end
    end

    def reset_approvals_cache(merge_request)
      merge_request.approvals.reset
    end
  end
end

MergeRequests::ApprovalService.prepend_mod_with('MergeRequests::ApprovalService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::ApprovalService, feature_category: :code_review_workflow do
  describe '#execute' do
    let(:user)          { create(:user) }
    let(:merge_request) { create(:merge_request, reviewers: [user]) }
    let(:project)       { merge_request.project }
    let!(:todo)         { create(:todo, user: user, project: project, target: merge_request) }

    subject(:service) { described_class.new(project: project, current_user: user) }

    before do
      project.add_developer(user)
      stub_feature_flags ff_require_saml_auth_to_approve: false
    end

    context 'with invalid approval' do
      before do
        allow(merge_request.approvals).to receive(:new).and_return(double(save: false))
      end

      it 'does not reset approvals' do
        expect(merge_request.approvals).not_to receive(:reset)

        service.execute(merge_request)
      end

      it 'does not track merge request approve action' do
        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .not_to receive(:track_approve_mr_action).with(user: user)

        service.execute(merge_request)
      end

      it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
        let(:action) { service.execute(merge_request) }
      end

      it 'does not publish MergeRequests::ApprovedEvent' do
        expect { service.execute(merge_request) }.not_to publish_event(MergeRequests::ApprovedEvent)
      end

      it_behaves_like 'does not trigger GraphQL subscription mergeRequestReviewersUpdated' do
        let(:action) { service.execute(merge_request) }
      end
    end

    context 'with an already approved MR' do
      before do
        merge_request.approvals.create!(user: user)
      end

      it 'does not create an approval' do
        expect { service.execute(merge_request) }.not_to change { merge_request.approvals.size }
      end

      it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
        let(:action) { service.execute(merge_request) }
      end

      it_behaves_like 'does not trigger GraphQL subscription mergeRequestReviewersUpdated' do
        let(:action) { service.execute(merge_request) }
      end
    end

    context 'with valid approval' do
      it 'resets approvals' do
        expect(merge_request.approvals).to receive(:reset)

        service.execute(merge_request)
      end

      it 'tracks merge request approve action' do
        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .to receive(:track_approve_mr_action).with(user: user, merge_request: merge_request)

        service.execute(merge_request)
      end

      context 'when generating a patch_id_sha' do
        it 'records a value' do
          service.execute(merge_request)

          expect(merge_request.approvals.last.patch_id_sha).to eq(merge_request.current_patch_id_sha)
        end

        context 'when MergeRequest#current_patch_id_sha is nil' do
          it 'records patch_id_sha as nil' do
            expect(merge_request).to receive(:current_patch_id_sha).and_return(nil)

            service.execute(merge_request)

            expect(merge_request.approvals.last.patch_id_sha).to be_nil
          end
        end
      end

      it 'publishes MergeRequests::ApprovedEvent' do
        expect { service.execute(merge_request) }
          .to publish_event(MergeRequests::ApprovedEvent)
          .with(current_user_id: user.id, merge_request_id: merge_request.id)
      end

      it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
        let(:action) { service.execute(merge_request) }
      end

      it_behaves_like 'triggers GraphQL subscription mergeRequestReviewersUpdated' do
        let(:action) { service.execute(merge_request) }
      end

      it_behaves_like 'triggers GraphQL subscription mergeRequestApprovalStateUpdated' do
        let(:action) { service.execute(merge_request) }
      end
    end

    context 'user cannot update the merge request' do
      before do
        project.add_guest(user)
      end

      it 'does not update approvals' do
        expect { service.execute(merge_request) }.not_to change { merge_request.approvals.size }
      end

      it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
        let(:action) { service.execute(merge_request) }
      end

      it_behaves_like 'does not trigger GraphQL subscription mergeRequestReviewersUpdated' do
        let(:action) { service.execute(merge_request) }
      end

      it_behaves_like 'does not trigger GraphQL subscription mergeRequestApprovalStateUpdated' do
        let(:action) { service.execute(merge_request) }
      end
    end
  end
end


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

module MergeRequests
  class AddTodoWhenBuildFailsService < MergeRequests::BaseService
    # Adds a todo to the parent merge_request when a CI build fails
    #
    def execute(commit_status)
      return if commit_status.allow_failure? || commit_status.retried?

      pipeline_merge_requests(commit_status.pipeline) do |merge_request|
        todo_service.merge_request_build_failed(merge_request)
      end
    end

    # Closes any pending build failed todos for the parent MRs when a
    # build is retried
    #
    def close(commit_status)
      close_all(commit_status.pipeline)
    end

    def close_all(pipeline)
      pipeline_merge_requests(pipeline) do |merge_request|
        todo_service.merge_request_build_retried(merge_request)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::MergeRequests::AddTodoWhenBuildFailsService, feature_category: :code_review_workflow do
  let(:user) { create(:user) }
  let(:project) { create(:project, :repository) }
  let(:sha) { '1234567890abcdef1234567890abcdef12345678' }
  let(:ref) { merge_request.source_branch }

  let(:service) do
    described_class.new(project: project, current_user: user, params: { commit_message: 'Awesome message' })
  end

  let(:todo_service) { spy('todo service') }

  let(:merge_request) do
    create(:merge_request, :with_detached_merge_request_pipeline, :opened, merge_user: user)
  end

  let(:pipeline) do
    merge_request.all_pipelines.take
  end

  before do
    allow_any_instance_of(MergeRequest)
      .to receive(:head_pipeline_id)
      .and_return(pipeline.id)

    allow(service).to receive(:todo_service).and_return(todo_service)
  end

  describe '#execute' do
    context 'commit status with ref' do
      let(:commit_status) do
        create(:generic_commit_status, ref: ref, pipeline: pipeline)
      end

      it 'notifies the todo service' do
        expect(todo_service).to receive(:merge_request_build_failed).with(merge_request)
        service.execute(commit_status)
      end
    end

    context 'commit status with non-HEAD ref' do
      let(:commit_status) { create(:generic_commit_status, ref: ref) }

      it 'does not notify the todo service' do
        expect(todo_service).not_to receive(:merge_request_build_failed)
        service.execute(commit_status)
      end
    end

    context 'commit status without ref' do
      let(:commit_status) { create(:generic_commit_status) }

      it 'does not notify the todo service' do
        expect(todo_service).not_to receive(:merge_request_build_failed)
        service.execute(commit_status)
      end
    end

    context 'when commit status is a build allowed to fail' do
      let(:commit_status) do
        create(:ci_build, :allowed_to_fail, ref: ref, pipeline: pipeline)
      end

      it 'does not create todo' do
        expect(todo_service).not_to receive(:merge_request_build_failed)

        service.execute(commit_status)
      end
    end

    context 'when build belongs to a merge request pipeline' do
      let(:pipeline) do
        create(
          :ci_pipeline,
          source: :merge_request_event,
          ref: merge_request.merge_ref_path,
          merge_request: merge_request,
          merge_requests_as_head_pipeline: [merge_request]
        )
      end

      let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) }

      it 'notifies the todo service' do
        expect(todo_service).to receive(:merge_request_build_failed).with(merge_request)
        service.execute(commit_status)
      end
    end
  end

  describe '#close' do
    context 'commit status with ref' do
      let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, pipeline: pipeline) }

      it 'notifies the todo service' do
        expect(todo_service).to receive(:merge_request_build_retried).with(merge_request)
        service.close(commit_status)
      end
    end

    context 'commit status with non-HEAD ref' do
      let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch) }

      it 'does not notify the todo service' do
        expect(todo_service).not_to receive(:merge_request_build_retried)
        service.close(commit_status)
      end
    end

    context 'commit status without ref' do
      let(:commit_status) { create(:generic_commit_status) }

      it 'does not notify the todo service' do
        expect(todo_service).not_to receive(:merge_request_build_retried)
        service.close(commit_status)
      end
    end

    context 'when build belongs to a merge request pipeline' do
      let(:pipeline) do
        create(
          :ci_pipeline,
          source: :merge_request_event,
          ref: merge_request.merge_ref_path,
          merge_request: merge_request,
          merge_requests_as_head_pipeline: [merge_request]
        )
      end

      let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) }

      it 'notifies the todo service' do
        expect(todo_service).to receive(:merge_request_build_retried).with(merge_request)
        service.close(commit_status)
      end
    end
  end

  describe '#close_all' do
    context 'when using pipeline that belongs to merge request' do
      it 'resolves todos about failed builds for pipeline' do
        service.close_all(pipeline)

        expect(todo_service)
          .to have_received(:merge_request_build_retried)
          .with(merge_request)
      end
    end

    context 'when pipeline is not related to merge request' do
      let(:pipeline) { create(:ci_empty_pipeline) }

      it 'does not resolve any todos about failed builds' do
        service.close_all(pipeline)

        expect(todo_service)
          .not_to have_received(:merge_request_build_retried)
      end
    end
  end
end


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

module MergeRequests
  class BuildService < MergeRequests::BaseService
    include Gitlab::Utils::StrongMemoize

    def execute
      @params_issue_iid = params.delete(:issue_iid)
      self.merge_request = MergeRequest.new
      # TODO: this should handle all quick actions that don't have side effects
      # https://gitlab.com/gitlab-org/gitlab-foss/issues/53658
      merge_quick_actions_into_params!(merge_request, only: [:target_branch])

      # Assign the projects first so we can use policies for `filter_params`
      merge_request.author = current_user
      merge_request.source_project = find_source_project
      merge_request.target_project = find_target_project

      initialize_callbacks!(merge_request)

      process_params

      merge_request.compare_commits = []
      set_merge_request_target_branch

      merge_request.can_be_created = projects_and_branches_valid?

      # compare branches only if branches are valid, otherwise
      # compare_branches may raise an error
      if merge_request.can_be_created
        compare_branches
        assign_title_and_description
        assign_labels
        assign_milestone
      end

      merge_request
    end

    private

    attr_accessor :merge_request

    delegate :target_branch,
      :target_branch_ref,
      :target_project,
      :source_branch,
      :source_branch_ref,
      :source_project,
      :compare_commits,
      :draft_title,
      :description,
      :first_multiline_commit,
      :errors,
      to: :merge_request

    def force_remove_source_branch
      if params.key?(:force_remove_source_branch)
        params.delete(:force_remove_source_branch)
      else
        merge_request.source_project.remove_source_branch_after_merge?
      end
    end

    def filter_id_params
      # merge_request.assign_attributes(...) below is a Rails
      # method that only work if all the params it is passed have
      # corresponding fields in the database. As there are no fields
      # in the database for :add_label_ids, :remove_label_ids,
      # :add_assignee_ids and :remove_assignee_ids, we
      # need to remove them from the params before the call to
      # merge_request.assign_attributes(...)
      #
      # IssuableBaseService#process_label_ids and
      # IssuableBaseService#process_assignee_ids take care
      # of the removal.
      params[:label_ids] = process_label_ids(params, issuable: merge_request, extra_label_ids: merge_request.label_ids.to_a)

      params[:assignee_ids] = process_assignee_ids(params, extra_assignee_ids: merge_request.assignee_ids.to_a)

      merge_request.assign_attributes(params.to_h.compact)
    end

    def process_params
      # Force remove the source branch?
      merge_request.merge_params['force_remove_source_branch'] = force_remove_source_branch

      # Only assign merge requests params that are allowed
      self.params = assign_allowed_merge_params(merge_request, params)

      # Filter out params that are either not allowed or invalid
      filter_params(merge_request)

      # Filter out the following from params:
      #  - :add_label_ids and :remove_label_ids
      #  - :add_assignee_ids and :remove_assignee_ids
      filter_id_params
    end

    def find_source_project
      source_project = project_from_params(:source_project)
      return source_project if source_project.present? && can?(current_user, :create_merge_request_from, source_project)

      project
    end

    def find_target_project
      target_project = project_from_params(:target_project)
      return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project)

      target_project = project.default_merge_request_target

      return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project)

      project
    end

    def project_from_params(param_name)
      project_from_params = params.delete(param_name)

      id_param_name = :"#{param_name}_id"
      if project_from_params.nil? && params[id_param_name]
        project_from_params = Project.find_by_id(params.delete(id_param_name))
      end

      project_from_params
    end

    def set_merge_request_target_branch
      if source_branch_default? && !target_branch_specified?
        merge_request.target_branch = nil
      else
        merge_request.target_branch ||= get_target_branch
      end
    end

    def get_target_branch
      target_project.default_branch
    end

    def source_branch_specified?
      params[:source_branch].present?
    end

    def target_branch_specified?
      params[:target_branch].present?
    end

    def projects_and_branches_valid?
      return false if source_project.nil? || target_project.nil?
      return false unless source_branch_specified? || target_branch_specified?

      validate_projects_and_branches
      errors.blank?
    end

    def compare_branches
      compare = CompareService.new(
        source_project,
        source_branch_ref
      ).execute(
        target_project,
        target_branch_ref
      )

      if compare
        merge_request.compare_commits = compare.commits
        merge_request.compare = compare
      end
    end

    def validate_projects_and_branches
      merge_request.validate_target_project
      merge_request.validate_fork

      return if errors.any?

      add_error('You must select source and target branch') unless branches_present?
      add_error('You must select different branches') if same_source_and_target?
      add_error("Source branch \"#{source_branch}\" does not exist") unless source_branch_exists?
      add_error("Target branch \"#{target_branch}\" does not exist") unless target_branch_exists?
    end

    def add_error(message)
      errors.add(:base, message)
    end

    def branches_present?
      target_branch.present? && source_branch.present?
    end

    def same_source_and_target?
      same_source_and_target_project? && target_branch == source_branch
    end

    def source_branch_default?
      same_source_and_target_project? && source_branch == target_project.default_branch
    end

    def same_source_and_target_project?
      source_project == target_project
    end

    def source_branch_exists?
      source_branch.blank? || source_project.commit(source_branch)
    end

    def target_branch_exists?
      target_branch.blank? || target_project.commit(target_branch)
    end

    def set_draft_title_if_needed
      return unless compare_commits.empty? || Gitlab::Utils.to_boolean(params[:draft])

      merge_request.title = draft_title
    end

    # When your branch name starts with an iid followed by a dash this pattern will be
    # interpreted as the user wants to close that issue on this project.
    #
    # For example:
    # - Issue 112 exists
    # - title: Emoji don't show up in commit title
    # - Source branch is: 112-fix-mep-mep
    #
    # Will lead to:
    # - Appending `Closes #112` to the description
    # - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is
    #   more than one commit in the MR
    #
    def assign_title_and_description
      assign_description_from_repository_template
      replace_variables_in_description
      assign_title_and_description_from_commits
      merge_request.title ||= title_from_issue if target_project.issues_enabled? || target_project.external_issue_tracker
      merge_request.title ||= source_branch.titleize.humanize
      set_draft_title_if_needed

      append_closes_description
    end

    def assign_labels
      return unless target_project.issues_enabled? && issue
      return if merge_request.label_ids&.any?

      merge_request.label_ids = issue.try(:label_ids)
    end

    def assign_milestone
      return unless target_project.issues_enabled? && issue
      return if merge_request.milestone_id.present?

      merge_request.milestone_id = issue.try(:milestone_id)
    end

    def append_closes_description
      return unless issue&.to_reference.present?

      closes_issue = "#{target_project.autoclose_referenced_issues ? 'Closes' : 'Related to'} #{issue.to_reference}"

      if description.present?
        descr_parts = [merge_request.description, closes_issue]
        merge_request.description = descr_parts.join("\n\n")
      else
        merge_request.description = closes_issue
      end
    end

    def assign_title_and_description_from_commits
      commits = compare_commits

      if commits&.count == 1
        commit = commits.first
      else
        commit = first_multiline_commit
        return unless commit
      end

      merge_request.title ||= commit.title
      merge_request.description ||= commit.description.try(:strip)
    end

    def title_from_issue
      return unless issue

      return "Resolve \"#{issue.title}\"" if issue.is_a?(Issue)

      return if issue_iid.blank?

      title_parts = ["Resolve #{issue.to_reference}"]
      branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize

      title_parts << "\"#{branch_title}\"" if branch_title.present?
      title_parts.join(' ')
    end

    def assign_description_from_repository_template
      return unless merge_request.description.blank?

      # Use TemplateFinder to load the default template. We need this mainly for
      # the project_id, in case it differs from the target project. Conveniently,
      # since the underlying merge_request_template_names_hash is cached, this
      # should also be relatively cheap and allows us to bail early if the project
      # does not have a default template.
      templates = TemplateFinder.all_template_names(target_project, :merge_requests)
      template = templates.values.flatten.find { |tmpl| tmpl[:name].casecmp?('default') }

      return unless template

      begin
        repository_template = TemplateFinder.build(
          :merge_requests,
          target_project,
          {
            name: template[:name],
            source_template_project_id: template[:project_id]
          }
        ).execute
      rescue ::Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
        return
      end

      return unless repository_template.present?

      merge_request.description = repository_template.content
    end

    def replace_variables_in_description
      return unless merge_request.description.present?

      merge_request.description = ::Gitlab::MergeRequests::MessageGenerator.new(
        merge_request: merge_request,
        current_user: current_user
      ).new_mr_description
    end

    def issue_iid
      strong_memoize(:issue_iid) do
        @params_issue_iid || begin
          id = if target_project.external_issue_tracker
                 target_project.external_issue_reference_pattern.match(source_branch).try(:[], 0)
               end

          id || source_branch.match(/\A(\d+)-/).try(:[], 1)
        end
      end
    end

    def issue
      strong_memoize(:issue) do
        target_project.get_issue(issue_iid, current_user)
      end
    end
  end
end

MergeRequests::BuildService.prepend_mod_with('MergeRequests::BuildService')

```

Response:
# frozen_string_literal: true
require 'spec_helper'

RSpec.describe MergeRequests::BuildService, feature_category: :code_review_workflow do
  using RSpec::Parameterized::TableSyntax
  include RepoHelpers
  include ProjectForksHelper

  let(:project) { create(:project, :repository) }
  let(:source_project) { nil }
  let(:target_project) { nil }
  let(:user) { create(:user) }
  let(:issue_confidential) { false }
  let(:issue) { create(:issue, project: project, title: 'A bug', confidential: issue_confidential) }
  let(:description) { nil }
  let(:source_branch) { 'feature-branch' }
  let(:target_branch) { 'master' }
  let(:milestone_id) { nil }
  let(:label_ids) { [] }
  let(:merge_request) { service.execute }
  let(:compare) { double(:compare, commits: commits) }
  let(:commit_1) do
    double(:commit_1,
      sha: 'f00ba6',
      safe_message: 'Initial commit',
      gitaly_commit?: false,
      id: 'f00ba6',
      parent_ids: ['f00ba5'],
      author_email: 'tom@example.com',
      author_name: 'Tom Example')
  end

  let(:commit_2) do
    double(:commit_2,
      sha: 'f00ba7',
      safe_message: "Closes #1234 Second commit\n\nCreate the app",
      gitaly_commit?: false,
      id: 'f00ba7',
      parent_ids: ['f00ba6'],
      author_email: 'alice@example.com',
      author_name: 'Alice Example')
  end

  let(:commit_3) do
    double(:commit_3,
      sha: 'f00ba8',
      safe_message: 'This is a bad commit message!',
      gitaly_commit?: false,
      id: 'f00ba8',
      parent_ids: ['f00ba7'],
      author_email: 'jo@example.com',
      author_name: 'Jo Example')
  end

  let(:commits) { nil }

  let(:params) do
    {
      description: description,
      source_branch: source_branch,
      target_branch: target_branch,
      source_project: source_project,
      target_project: target_project,
      milestone_id: milestone_id,
      label_ids: label_ids
    }
  end

  let(:service) do
    described_class.new(project: project, current_user: user, params: params)
  end

  before do
    project.add_guest(user)
  end

  def stub_compare
    allow(CompareService).to receive_message_chain(:new, :execute).and_return(compare)
    allow(project).to receive(:commit).and_return(commit_1)
    allow(project).to receive(:commit).and_return(commit_2)
    allow(project).to receive(:commit).and_return(commit_3)
  end

  shared_examples 'allows the merge request to be created' do
    it do
      expect(merge_request.can_be_created).to eq(true)
    end
  end

  shared_examples 'forbids the merge request from being created' do
    it 'returns that the merge request cannot be created' do
      expect(merge_request.can_be_created).to eq(false)
    end

    it 'adds an error message to the merge request' do
      expect(merge_request.errors).to contain_exactly(*Array(error_message))
    end
  end

  shared_examples 'with a Default.md template' do
    let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
    let(:project) { create(:project, :custom_repo, files: files) }

    it 'the template description is preferred' do
      expect(merge_request.description).to eq('Default template contents')
    end
  end

  describe '#execute' do
    it 'calls the compare service with the correct arguments' do
      allow_any_instance_of(described_class).to receive(:projects_and_branches_valid?).and_return(true)
      expect(CompareService).to receive(:new)
                                  .with(project, Gitlab::Git::BRANCH_REF_PREFIX + source_branch)
                                  .and_call_original

      expect_any_instance_of(CompareService).to receive(:execute)
                                                  .with(project, Gitlab::Git::BRANCH_REF_PREFIX + target_branch)
                                                  .and_call_original

      merge_request
    end

    it 'does not assign force_remove_source_branch' do
      expect(merge_request.force_remove_source_branch?).to be_truthy
    end

    context 'with force_remove_source_branch parameter when the user is authorized' do
      let(:mr_params) { params.merge(force_remove_source_branch: '1') }
      let(:source_project) { fork_project(project, user) }
      let(:merge_request) { described_class.new(project: project, current_user: user, params: mr_params).execute }

      before do
        project.add_reporter(user)
      end

      it 'assigns force_remove_source_branch' do
        expect(merge_request.force_remove_source_branch?).to be_truthy
      end

      context 'with project setting remove_source_branch_after_merge false' do
        before do
          project.remove_source_branch_after_merge = false
        end

        it 'assigns force_remove_source_branch' do
          expect(merge_request.force_remove_source_branch?).to be_truthy
        end
      end
    end

    context 'with project setting remove_source_branch_after_merge true' do
      before do
        project.remove_source_branch_after_merge = true
      end

      it 'assigns force_remove_source_branch' do
        expect(merge_request.force_remove_source_branch?).to be_truthy
      end

      context 'with force_remove_source_branch parameter false' do
        before do
          params[:force_remove_source_branch] = '0'
        end

        it 'does not assign force_remove_source_branch' do
          expect(merge_request.force_remove_source_branch?).to be(false)
        end
      end
    end

    context 'missing source branch' do
      let(:source_branch) { '' }

      it_behaves_like 'forbids the merge request from being created' do
        let(:error_message) { 'You must select source and target branch' }
      end
    end

    context 'when target branch is missing' do
      let(:target_branch) { nil }
      let(:commits) { Commit.decorate([commit_2], project) }

      before do
        stub_compare
      end

      context 'when source branch' do
        context 'is not the repository default branch' do
          it 'creates compare object with target branch as default branch' do
            expect(merge_request.compare).to be_present
            expect(merge_request.target_branch).to eq(project.default_branch)
          end

          it_behaves_like 'allows the merge request to be created'
        end

        context 'the repository default branch' do
          let(:source_branch) { 'master' }

          it_behaves_like 'forbids the merge request from being created' do
            let(:error_message) { 'You must select source and target branch' }
          end

          context 'when source project is different from the target project' do
            let(:target_project) { create(:project, :public, :repository) }
            let!(:project) { fork_project(target_project, user, namespace: user.namespace, repository: true) }
            let(:source_project) { project }

            it 'creates compare object with target branch as default branch', :sidekiq_might_not_need_inline do
              expect(merge_request.compare).to be_present
              expect(merge_request.target_branch).to eq(project.default_branch)
            end

            it_behaves_like 'allows the merge request to be created'
          end
        end
      end
    end

    context 'same source and target branch' do
      let(:source_branch) { 'master' }

      it_behaves_like 'forbids the merge request from being created' do
        let(:error_message) { 'You must select different branches' }
      end
    end

    context 'no commits in the diff' do
      let(:commits) { [] }

      before do
        stub_compare
      end

      it_behaves_like 'allows the merge request to be created'

      it 'adds a Draft prefix to the merge request title' do
        expect(merge_request.title).to eq('Draft: Feature branch')
      end
    end

    context 'one commit in the diff' do
      let(:commits) { Commit.decorate([commit_2], project) }
      let(:commit_description) { commit_2.safe_message.split(/\n+/, 2).last }

      before do
        stub_compare
      end

      it_behaves_like 'allows the merge request to be created'
      it_behaves_like 'with a Default.md template'

      it 'uses the title of the commit as the title of the merge request' do
        expect(merge_request.title).to eq(commit_2.safe_message.split("\n").first)
      end

      it 'uses the description of the commit as the description of the merge request' do
        expect(merge_request.description).to eq(commit_description)
      end

      context 'merge request already has a description set' do
        let(:description) { 'Merge request description' }

        it 'keeps the description from the initial params' do
          expect(merge_request.description).to eq(description)
        end
      end

      context 'commit has no description' do
        let(:commits) { Commit.decorate([commit_3], project) }

        it_behaves_like 'with a Default.md template'

        it 'uses the title of the commit as the title of the merge request' do
          expect(merge_request.title).to eq(commit_3.safe_message)
        end

        it 'sets the description to nil' do
          expect(merge_request.description).to be_nil
        end
      end

      context 'when the source branch matches an issue' do
        where(:factory, :source_branch, :closing_message) do
          :jira_integration | 'FOO-123-fix-issue' | 'Closes FOO-123'
          :jira_integration | 'fix-issue'         | nil
          :custom_issue_tracker_integration | '123-fix-issue'     | 'Closes #123'
          :custom_issue_tracker_integration | 'fix-issue'         | nil
          nil | '123-fix-issue'     | 'Closes #123'
          nil | 'fix-issue'         | nil
        end

        with_them do
          before do
            if factory
              create(factory, project: project)
              project.reload
            else
              issue.update!(iid: 123)
            end
          end

          it 'uses the title of the commit as the title of the merge request' do
            expect(merge_request.title).to eq('Closes #1234 Second commit')
          end

          it 'appends the closing description' do
            expected_description = [commit_description, closing_message].compact.join("\n\n")

            expect(merge_request.description).to eq(expected_description)
          end

          context 'a Default.md template is defined' do
            let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
            let(:project) { create(:project, :custom_repo, files: files) }

            it 'appends the closing description to a Default.md template' do
              expected_description = ['Default template contents', closing_message].compact.join("\n\n")

              expect(merge_request.description).to eq(expected_description)
            end
          end
        end

        context 'when the source branch matches an internal issue' do
          let(:label) { create(:label, project: project) }
          let(:milestone) { create(:milestone, project: project) }
          let(:source_branch) { '123-fix-issue' }

          before do
            issue.update!(iid: 123, labels: [label], milestone: milestone)
          end

          it 'assigns the issue label and milestone' do
            expect(merge_request.milestone).to eq(milestone)
            expect(merge_request.labels).to match_array([label])
          end

          context 'when milestone_id and label_ids are shared in the params' do
            let(:label2) { create(:label, project: project) }
            let(:milestone2) { create(:milestone, project: project) }
            let(:label_ids) { [label2.id] }
            let(:milestone_id) { milestone2.id }

            before do
              # Guests are not able to assign labels or milestones to an issue
              project.add_developer(user)
            end

            it 'assigns milestone_id and label_ids instead of issue labels and milestone' do
              expect(merge_request.milestone).to eq(milestone2)
              expect(merge_request.labels).to match_array([label2])
            end
          end
        end

        context 'when a milestone is from another project' do
          let(:milestone) { create(:milestone, project: create(:project)) }
          let(:milestone_id) { milestone.id }

          it 'sets milestone to nil' do
            expect(merge_request.milestone).to be_nil
          end
        end
      end
    end

    context 'no multi-line commit messages in the diff' do
      let(:commits) { Commit.decorate([commit_1, commit_3], project) }

      before do
        stub_compare
      end

      it_behaves_like 'allows the merge request to be created'
      it_behaves_like 'with a Default.md template'

      it 'uses the title of the branch as the merge request title' do
        expect(merge_request.title).to eq('Feature branch')
      end

      it 'does not add a description' do
        expect(merge_request.description).to be_nil
      end

      context 'merge request already has a description set' do
        let(:description) { 'Merge request description' }

        it 'keeps the description from the initial params' do
          expect(merge_request.description).to eq(description)
        end

        context 'a Default.md template is defined' do
          let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
          let(:project) { create(:project, :custom_repo, files: files) }

          it 'keeps the description from the initial params' do
            expect(merge_request.description).to eq(description)
          end
        end
      end

      context 'when the source branch matches an issue' do
        where(:factory, :source_branch, :title, :closing_message) do
          :jira_integration | 'FOO-123-fix-issue' | 'Resolve FOO-123 "Fix issue"' | 'Closes FOO-123'
          :jira_integration | 'fix-issue'         | 'Fix issue'                   | nil
          :custom_issue_tracker_integration | '123-fix-issue'     | 'Resolve #123 "Fix issue"'    | 'Closes #123'
          :custom_issue_tracker_integration | 'fix-issue'         | 'Fix issue'                   | nil
          nil | '123-fix-issue'     | 'Resolve "A bug"'             | 'Closes #123'
          nil | 'fix-issue'         | 'Fix issue'                   | nil
          nil | '124-fix-issue'     | '124 fix issue'               | nil
        end

        with_them do
          before do
            if factory
              create(factory, project: project)
              project.reload
            else
              issue.update!(iid: 123)
            end
          end

          it 'sets the correct title' do
            expect(merge_request.title).to eq(title)
          end

          it 'sets the closing description' do
            expect(merge_request.description).to eq(closing_message)
          end

          context 'a Default.md template is defined' do
            let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
            let(:project) { create(:project, :custom_repo, files: files) }

            it 'appends the closing description to a Default.md template' do
              expected_description = ['Default template contents', closing_message].compact.join("\n\n")

              expect(merge_request.description).to eq(expected_description)
            end
          end
        end
      end
    end

    context 'a multi-line commit message in the diff' do
      let(:commits) { Commit.decorate([commit_1, commit_2, commit_3], project) }

      before do
        stub_compare
      end

      it_behaves_like 'allows the merge request to be created'
      it_behaves_like 'with a Default.md template'

      it 'uses the first line of the first multi-line commit message as the title' do
        expect(merge_request.title).to eq('Closes #1234 Second commit')
      end

      it 'adds the remaining lines of the first multi-line commit message as the description' do
        expect(merge_request.description).to eq('Create the app')
      end

      context 'when the source branch matches an issue' do
        where(:factory, :source_branch, :title, :closing_message) do
          :jira_integration | 'FOO-123-fix-issue' | 'Resolve FOO-123 "Fix issue"' | 'Closes FOO-123'
          :jira_integration | 'fix-issue'         | 'Fix issue'                   | nil
          :custom_issue_tracker_integration | '123-fix-issue'     | 'Resolve #123 "Fix issue"'    | 'Closes #123'
          :custom_issue_tracker_integration | 'fix-issue'         | 'Fix issue'                   | nil
          nil | '123-fix-issue'     | 'Resolve "A bug"'             | 'Closes #123'
          nil | 'fix-issue'         | 'Fix issue'                   | nil
          nil | '124-fix-issue'     | '124 fix issue'               | nil
        end

        with_them do
          before do
            if factory
              create(factory, project: project)
              project.reload
            else
              issue.update!(iid: 123)
            end
          end

          it 'sets the correct title' do
            expect(merge_request.title).to eq('Closes #1234 Second commit')
          end

          it 'sets the closing description' do
            expect(merge_request.description).to eq("Create the app#{closing_message ? "\n\n" + closing_message : ''}")
          end

          context 'a Default.md template is defined' do
            let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
            let(:project) { create(:project, :custom_repo, files: files) }

            it 'appends the closing description to a Default.md template' do
              expected_description = ['Default template contents', closing_message].compact.join("\n\n")

              expect(merge_request.description).to eq(expected_description)
            end
          end
        end
      end

      context 'when the issue is not accessible to user' do
        let(:source_branch) { "#{issue.iid}-fix-issue" }

        before do
          project.team.truncate
        end

        it 'uses the first line of the first multi-line commit message as the title' do
          expect(merge_request.title).to eq('Closes #1234 Second commit')
        end

        it 'adds the remaining lines of the first multi-line commit message as the description', :sidekiq_inline do
          expect(merge_request.description).to eq('Create the app')
        end
      end

      context 'when the issue is confidential' do
        let(:source_branch) { "#{issue.iid}-fix-issue" }
        let(:issue_confidential) { true }

        it 'uses the first line of the first multi-line commit message as the title' do
          expect(merge_request.title).to eq('Closes #1234 Second commit')
        end

        it 'adds the remaining lines of the first multi-line commit message as the description' do
          expect(merge_request.description).to eq('Create the app')
        end
      end
    end

    context 'source branch does not exist' do
      before do
        allow(project).to receive(:commit).with(source_branch).and_return(nil)
        allow(project).to receive(:commit).with(target_branch).and_return(commit_2)
      end

      it_behaves_like 'forbids the merge request from being created' do
        let(:error_message) { 'Source branch "feature-branch" does not exist' }
      end
    end

    context 'target branch does not exist' do
      before do
        allow(project).to receive(:commit).with(source_branch).and_return(commit_2)
        allow(project).to receive(:commit).with(target_branch).and_return(nil)
      end

      it_behaves_like 'forbids the merge request from being created' do
        let(:error_message) { 'Target branch "master" does not exist' }
      end
    end

    context 'both source and target branches do not exist' do
      before do
        allow(project).to receive(:commit).and_return(nil)
      end

      it_behaves_like 'forbids the merge request from being created' do
        let(:error_message) do
          ['Source branch "feature-branch" does not exist', 'Target branch "master" does not exist']
        end
      end
    end

    context 'upstream project has disabled merge requests' do
      let(:upstream_project) { create(:project, :merge_requests_disabled) }
      let(:project) { create(:project, forked_from_project: upstream_project) }
      let(:commits) { Commit.decorate([commit_2], project) }

      it 'sets target project correctly' do
        expect(merge_request.target_project).to eq(project)
      end
    end

    context 'target_project is set and accessible by current_user' do
      let(:target_project) { create(:project, :public, :repository) }
      let(:commits) { Commit.decorate([commit_2], project) }

      it 'sets target project correctly' do
        expect(merge_request.target_project).to eq(target_project)
      end
    end

    context 'target_project is set but not accessible by current_user' do
      let(:target_project) { create(:project, :private, :repository) }
      let(:commits) { Commit.decorate([commit_2], project) }

      it 'sets target project correctly' do
        expect(merge_request.target_project).to eq(project)
      end
    end

    context 'target_project is set but repo is not accessible by current_user' do
      let(:target_project) do
        create(:project, :public, :repository, repository_access_level: ProjectFeature::PRIVATE)
      end

      it 'sets target project correctly' do
        expect(merge_request.target_project).to eq(project)
      end
    end

    context 'source_project is set and accessible by current_user' do
      let(:source_project) { create(:project, :public, :repository) }
      let(:commits) { Commit.decorate([commit_2], project) }

      before do
        # To create merge requests _from_ a project the user needs at least
        # developer access
        source_project.add_developer(user)
      end

      it 'sets source project correctly' do
        expect(merge_request.source_project).to eq(source_project)
      end
    end

    context 'source_project is set but not accessible by current_user' do
      let(:source_project) { create(:project, :private, :repository) }
      let(:commits) { Commit.decorate([commit_2], project) }

      it 'sets source project correctly' do
        expect(merge_request.source_project).to eq(project)
      end
    end

    context 'source_project is set but the user cannot create merge requests from the project' do
      let(:source_project) do
        create(:project, :public, :repository, merge_requests_access_level: ProjectFeature::PRIVATE)
      end

      it 'sets the source_project correctly' do
        expect(merge_request.source_project).to eq(project)
      end
    end

    context 'target_project is not in the fork network of source_project' do
      let(:target_project) { create(:project, :public, :repository) }

      it 'adds an error to the merge request' do
        expect(merge_request.errors[:validate_fork]).to contain_exactly('Source project is not a fork of the target project')
      end
    end

    context 'target_project is in the fork network of source project but no longer accessible' do
      let!(:project) { fork_project(target_project, user, namespace: user.namespace, repository: true) }
      let(:source_project) { project }
      let(:target_project) { create(:project, :public, :repository) }

      before do
        target_project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
      end

      it 'sets the target_project correctly' do
        expect(merge_request.target_project).to eq(project)
      end
    end

    context 'when specifying target branch in the description' do
      let(:description) { "A merge request targeting another branch\n\n/target_branch with-codeowners" }

      it 'sets the attribute from the quick actions' do
        expect(merge_request.target_branch).to eq('with-codeowners')
      end
    end
  end

  context 'when assigning labels' do
    let(:label_ids) { [create(:label, project: project).id] }

    context 'for members with less than developer access' do
      it 'is not allowed' do
        expect(merge_request.label_ids).to be_empty
      end
    end

    context 'for users allowed to assign labels' do
      before do
        project.add_developer(user)
      end

      context 'for labels in the project' do
        it 'is allowed for developers' do
          expect(merge_request.label_ids).to contain_exactly(*label_ids)
        end
      end

      context 'for unrelated labels' do
        let(:project_label) { create(:label, project: project) }
        let(:label_ids) { [create(:label).id, project_label.id] }

        it 'only assigns related labels' do
          expect(merge_request.label_ids).to contain_exactly(project_label.id)
        end
      end
    end
  end

  describe '#assign_description_from_repository_template' do
    subject { service.send(:assign_description_from_repository_template) }

    it 'performs no action if the merge request description is not blank' do
      merge_request.description = 'foo'
      subject
      expect(merge_request.description).to eq 'foo'
    end

    context 'when a Default template is not found' do
      it 'does not modify the merge request description' do
        merge_request.description = nil
        subject
        expect(merge_request.description).to be_nil
      end
    end

    context 'when a Default template is found' do
      context 'when its contents cannot be retrieved' do
        let(:files) { { '.gitlab/merge_request_templates/OtherTemplate.md' => 'Other template contents' } }
        let(:project) { create(:project, :custom_repo, files: files) }

        it 'does not modify the merge request description' do
          allow(TemplateFinder).to receive(:all_template_names).and_return({
            merge_requests: [
              { name: 'Default', id: 'default', key: 'default', project_id: project.id }
            ]
          })

          merge_request.description = nil
          subject
          expect(merge_request.description).to be_nil
        end
      end

      context 'when its contents can be retrieved' do
        let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
        let(:project) { create(:project, :custom_repo, files: files) }

        it 'modifies the merge request description' do
          merge_request.description = nil
          subject
          expect(merge_request.description).to eq 'Default template contents'
        end
      end
    end
  end

  describe '#replace_variables_in_description' do
    context 'when the merge request description is blank' do
      let(:description) { nil }

      it 'does not update the description' do
        expect(merge_request.description).to eq(nil)
      end
    end

    context 'when the merge request description contains template variables' do
      let(:description) { <<~MSG.rstrip }
        source_branch:%{source_branch}
        target_branch:%{target_branch}
        title:%{title}
        issues:%{issues}
        description:%{description}
        first_commit:%{first_commit}
        first_multiline_commit:%{first_multiline_commit}
        url:%{url}
        approved_by:%{approved_by}
        merged_by:%{merged_by}
        co_authored_by:%{co_authored_by}
        all_commits:%{all_commits}
      MSG

      context 'when there are multiple commits in the diff' do
        let(:commits) { Commit.decorate([commit_1, commit_2, commit_3], project) }

        before do
          stub_compare
        end

        it 'replaces the variables in the description' do
          expect(merge_request.description).to eq <<~MSG.rstrip
            source_branch:feature-branch
            target_branch:master
            title:
            issues:
            description:
            first_commit:Initial commit
            first_multiline_commit:Closes #1234 Second commit

            Create the app
            url:
            approved_by:
            merged_by:
            co_authored_by:Co-authored-by: Jo Example <jo@example.com>
            Co-authored-by: Alice Example <alice@example.com>
            Co-authored-by: Tom Example <tom@example.com>
            all_commits:* This is a bad commit message!

            * Closes #1234 Second commit

            Create the app

            * Initial commit
          MSG
        end
      end

      context 'when there are no commits in the diff' do
        let(:commits) { [] }

        before do
          stub_compare
        end

        it 'replaces the variables in the description' do
          expect(merge_request.description).to eq <<~MSG.rstrip
            source_branch:feature-branch
            target_branch:master
            title:
            issues:
            description:
            first_commit:
            first_multiline_commit:
            url:
            approved_by:
            merged_by:
            co_authored_by:
            all_commits:
          MSG
        end
      end
    end
  end
end


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

module MergeRequests
  class ResolveTodosService
    include BaseServiceUtility

    def initialize(merge_request, user)
      @merge_request = merge_request
      @user = user
    end

    def async_execute
      MergeRequests::ResolveTodosWorker.perform_async(merge_request.id, user.id)
    end

    def execute
      todo_service.resolve_todos_for_target(merge_request, user)
    end

    private

    attr_reader :merge_request, :user
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::ResolveTodosService, feature_category: :code_review_workflow do
  let_it_be(:merge_request) { create(:merge_request) }
  let_it_be(:user) { create(:user) }

  let(:service) { described_class.new(merge_request, user) }

  describe '#async_execute' do
    def async_execute
      service.async_execute
    end

    it 'performs MergeRequests::ResolveTodosWorker asynchronously' do
      expect(MergeRequests::ResolveTodosWorker)
        .to receive(:perform_async)
        .with(
          merge_request.id,
          user.id
        )

      async_execute
    end
  end

  describe '#execute' do
    it 'marks pending todo as done' do
      pending_todo = create(:todo, :pending, user: user, project: merge_request.project, target: merge_request)

      service.execute

      expect(pending_todo.reload).to be_done
    end
  end
end