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

module MergeRequests
  # CreateRefService creates or overwrites a ref under "refs/merge-requests/"
  # with a commit for the merged result.
  class CreateRefService
    include Gitlab::Utils::StrongMemoize

    CreateRefError = Class.new(StandardError)

    def initialize(
      current_user:, merge_request:, target_ref:, first_parent_ref:, source_sha: nil
    )
      @current_user = current_user
      @merge_request = merge_request
      @source_sha = source_sha
      @target_ref = target_ref
      @first_parent_ref = first_parent_ref
      @first_parent_sha = target_project.commit(first_parent_ref)&.sha
    end

    def execute
      # TODO: Update this message with the removal of FF merge_trains_create_ref_service and update tests
      # This is for compatibility with MergeToRefService during the rollout.
      return ServiceResponse.error(message: '3:Invalid merge source') unless first_parent_sha.present?

      result = {
        commit_sha: source_sha,      # the SHA to be at HEAD of target_ref
        expected_old_oid: "",        # the SHA we expect target_ref to be at prior to an update (an optimistic lock)
        source_sha: source_sha,      # for pipeline.source_sha
        target_sha: first_parent_sha # for pipeline.target_sha
      }

      result = maybe_squash!(**result)
      result = maybe_rebase!(**result)
      result = maybe_merge!(**result)

      update_merge_request!(merge_request, result)

      ServiceResponse.success(payload: result)
    rescue CreateRefError => error
      ServiceResponse.error(message: error.message)
    end

    private

    attr_reader :current_user, :merge_request, :target_ref, :first_parent_ref, :first_parent_sha, :source_sha

    delegate :target_project, to: :merge_request
    delegate :repository, to: :target_project

    def maybe_squash!(commit_sha:, **rest)
      if merge_request.squash_on_merge?
        squash_result = MergeRequests::SquashService.new(
          merge_request: merge_request,
          current_user: current_user,
          commit_message: squash_commit_message
        ).execute

        raise CreateRefError, squash_result[:message] if squash_result[:status] == :error

        commit_sha = squash_result[:squash_sha]
        squash_commit_sha = commit_sha
      end

      # squash does not overwrite target_ref, so expected_old_oid remains the same
      rest.merge(
        commit_sha: commit_sha,
        squash_commit_sha: squash_commit_sha
      ).compact
    end

    def maybe_rebase!(commit_sha:, expected_old_oid:, squash_commit_sha: nil, **rest)
      if target_project.ff_merge_must_be_possible?
        commit_sha = safe_gitaly_operation do
          repository.rebase_to_ref(
            current_user,
            source_sha: commit_sha,
            target_ref: target_ref,
            first_parent_ref: first_parent_sha,
            expected_old_oid: expected_old_oid || ""
          )
        end

        squash_commit_sha = commit_sha if squash_commit_sha # rebase rewrites commit SHAs after first_parent_sha
        expected_old_oid = commit_sha
      end

      rest.merge(
        commit_sha: commit_sha,
        squash_commit_sha: squash_commit_sha,
        expected_old_oid: expected_old_oid
      ).compact
    end

    def maybe_merge!(commit_sha:, expected_old_oid:, **rest)
      unless target_project.merge_requests_ff_only_enabled
        commit_sha = safe_gitaly_operation do
          repository.merge_to_ref(
            current_user,
            source_sha: commit_sha,
            target_ref: target_ref,
            message: merge_commit_message,
            first_parent_ref: first_parent_sha,
            branch: nil,
            expected_old_oid: expected_old_oid || ""
          )
        end

        expected_old_oid = commit_sha
        merge_commit_sha = commit_sha
      end

      rest.merge(
        commit_sha: commit_sha,
        merge_commit_sha: merge_commit_sha,
        expected_old_oid: expected_old_oid
      ).compact
    end

    def update_merge_request!(merge_request, result)
      # overridden in EE
    end

    def safe_gitaly_operation
      yield
    rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError, ArgumentError => error
      raise CreateRefError, error.message
    end

    def squash_commit_message
      merge_request.merge_params['squash_commit_message'].presence ||
        merge_request.default_squash_commit_message(user: current_user)
    end
    strong_memoize_attr :squash_commit_message

    def merge_commit_message
      merge_request.merge_params['commit_message'].presence ||
        merge_request.default_merge_commit_message(user: current_user)
    end
  end
end

MergeRequests::CreateRefService.prepend_mod_with('MergeRequests::CreateRefService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::CreateRefService, feature_category: :merge_trains do
  using RSpec::Parameterized::TableSyntax

  describe '#execute' do
    let_it_be_with_reload(:project) { create(:project, :empty_repo) }
    let_it_be(:user) { project.creator }
    let_it_be(:first_parent_ref) { project.default_branch_or_main }
    let_it_be(:source_branch) { 'branch' }
    let(:target_ref) { "refs/merge-requests/#{merge_request.iid}/train" }
    let(:source_sha) { project.commit(source_branch).sha }
    let(:squash) { false }
    let(:default_commit_message) { merge_request.default_merge_commit_message(user: user) }

    let(:merge_request) do
      create(
        :merge_request,
        title: 'Merge request ref test',
        author: user,
        source_project: project,
        target_project: project,
        source_branch: source_branch,
        target_branch: first_parent_ref,
        squash: squash
      )
    end

    subject(:result) do
      described_class.new(
        current_user: user,
        merge_request: merge_request,
        target_ref: target_ref,
        source_sha: source_sha,
        first_parent_ref: first_parent_ref
      ).execute
    end

    context 'when there is a user-caused gitaly error' do
      let(:source_sha) { '123' }

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

    context 'with valid inputs' do
      before_all do
        # ensure first_parent_ref is created before source_sha
        project.repository.create_file(
          user,
          'README.md',
          '',
          message: 'Base parent commit 1',
          branch_name: first_parent_ref
        )
        project.repository.create_branch(source_branch, first_parent_ref)

        # create two commits source_branch to test squashing
        project.repository.create_file(
          user,
          '.gitlab-ci.yml',
          '',
          message: 'Feature branch commit 1',
          branch_name: source_branch
        )

        project.repository.create_file(
          user,
          '.gitignore',
          '',
          message: 'Feature branch commit 2',
          branch_name: source_branch
        )

        # create an extra commit not present on source_branch
        project.repository.create_file(
          user,
          'EXTRA',
          '',
          message: 'Base parent commit 2',
          branch_name: first_parent_ref
        )
      end

      shared_examples_for 'writing with a merge commit' do
        it 'merges with a merge commit', :aggregate_failures do
          expect(result[:status]).to eq :success
          expect(result[:commit_sha]).to eq(project.repository.commit(target_ref).sha)
          expect(result[:source_sha]).to eq(project.repository.commit(source_branch).sha)
          expect(result[:target_sha]).to eq(project.repository.commit(first_parent_ref).sha)
          expect(result[:merge_commit_sha]).to be_present
          expect(result[:squash_commit_sha]).not_to be_present
          expect(project.repository.commits(target_ref, limit: 10, order: 'topo').map(&:message)).to(
            match(
              [
                expected_merge_commit,
                'Feature branch commit 2',
                'Feature branch commit 1',
                'Base parent commit 2',
                'Base parent commit 1'
              ]
            )
          )
        end
      end

      shared_examples_for 'writing with a squash and merge commit' do
        it 'writes the squashed result', :aggregate_failures do
          expect(result[:status]).to eq :success
          expect(result[:commit_sha]).to eq(project.repository.commit(target_ref).sha)
          expect(result[:source_sha]).to eq(project.repository.commit(source_branch).sha)
          expect(result[:target_sha]).to eq(project.repository.commit(first_parent_ref).sha)
          expect(result[:merge_commit_sha]).to be_present
          expect(result[:squash_commit_sha]).to be_present
          expect(project.repository.commits(target_ref, limit: 10, order: 'topo').map(&:message)).to(
            match(
              [
                expected_merge_commit,
                "#{merge_request.title}\n",
                'Base parent commit 2',
                'Base parent commit 1'
              ]
            )
          )
        end
      end

      shared_examples_for 'writing with a squash and no merge commit' do
        it 'writes the squashed result without a merge commit', :aggregate_failures do
          expect(result[:status]).to eq :success
          expect(result[:commit_sha]).to eq(project.repository.commit(target_ref).sha)
          expect(result[:source_sha]).to eq(project.repository.commit(source_branch).sha)
          expect(result[:target_sha]).to eq(project.repository.commit(first_parent_ref).sha)
          expect(result[:merge_commit_sha]).not_to be_present
          expect(result[:squash_commit_sha]).to be_present
          expect(project.repository.commits(target_ref, limit: 10, order: 'topo').map(&:message)).to(
            match(
              [
                "#{merge_request.title}\n",
                'Base parent commit 2',
                'Base parent commit 1'
              ]
            )
          )
        end
      end

      shared_examples_for 'writing without a merge commit' do
        it 'writes the rebased merged result', :aggregate_failures do
          expect(result[:status]).to eq :success
          expect(result[:commit_sha]).to eq(project.repository.commit(target_ref).sha)
          expect(result[:source_sha]).to eq(project.repository.commit(source_branch).sha)
          expect(result[:target_sha]).to eq(project.repository.commit(first_parent_ref).sha)
          expect(result[:merge_commit_sha]).not_to be_present
          expect(result[:squash_commit_sha]).not_to be_present
          expect(project.repository.commits(target_ref, limit: 10, order: 'topo').map(&:message)).to(
            eq(
              [
                'Feature branch commit 2',
                'Feature branch commit 1',
                'Base parent commit 2',
                'Base parent commit 1'
              ]
            )
          )
        end
      end

      shared_examples 'merge commits without squash' do
        context 'with a custom template' do
          let(:expected_merge_commit) { 'This is the merge commit' } # could also be default_commit_message

          before do
            project.project_setting.update!(merge_commit_template: expected_merge_commit)
          end

          it_behaves_like 'writing with a merge commit'
        end

        context 'with no custom template' do
          let(:expected_merge_commit) { default_commit_message }

          before do
            project.project_setting.update!(merge_commit_template: nil)
          end

          it_behaves_like 'writing with a merge commit'
        end
      end

      shared_examples 'merge commits with squash' do
        context 'when squash set' do
          let(:squash) { true }
          let(:expected_merge_commit) { merge_request.default_merge_commit_message(user: user) }

          before do
            project.project_setting.update!(merge_commit_template: 'This is the merge commit')
          end

          it_behaves_like 'writing with a squash and merge commit'
        end
      end

      context 'when the merge commit message is provided at time of merge' do
        let(:expected_merge_commit) { 'something custom' }

        before do
          merge_request.merge_params['commit_message'] = expected_merge_commit
        end

        it 'writes the merged result', :aggregate_failures do
          expect(result[:status]).to eq :success
          expect(project.repository.commits(target_ref, limit: 1, order: 'topo').map(&:message)).to(
            match([expected_merge_commit])
          )
        end

        context 'when squash set' do
          let(:squash) { true }

          it_behaves_like 'writing with a squash and merge commit'
        end
      end

      context 'when merged commit strategy' do
        include_examples 'merge commits without squash'
        include_examples 'merge commits with squash'
      end

      context 'when semi-linear merge strategy' do
        before do
          project.merge_method = :rebase_merge
          project.save!
        end

        include_examples 'merge commits without squash'
        include_examples 'merge commits with squash'

        context 'when the target ref changes between rebase and merge' do
          # this tests internal handling of expected_old_oid

          it 'returns an error', :aggregate_failures do
            expect_next_instance_of(described_class) do |instance|
              original = instance.method(:maybe_merge!)

              expect(instance).to receive(:maybe_merge!) do |*args, **kwargs|
                # Corrupt target_ref before the merge, simulating a race with
                # another instance of the service for the same MR. source_sha is
                # just an arbitrary valid commit that differs from what was just
                # written.
                project.repository.write_ref(target_ref, source_sha)
                original.call(*args, **kwargs)
              end
            end

            expect(result[:status]).to eq :error
            expect(result[:message]).to eq "9:Could not update #{target_ref}. Please refresh and try again."
          end
        end
      end

      context 'when fast-forward merge strategy' do
        before do
          project.merge_method = :ff
          project.save!
        end

        it_behaves_like 'writing without a merge commit'

        context 'when squash set' do
          let(:squash) { true }

          it_behaves_like 'writing with a squash and no merge commit'
        end
      end
    end
  end
end


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

module MergeRequests
  # MergeService class
  #
  # Do git merge and in case of success
  # mark merge request as merged and execute all hooks and notifications
  # Executed when you do merge via GitLab UI
  #
  class MergeService < MergeRequests::MergeBaseService
    include Gitlab::Utils::StrongMemoize

    GENERIC_ERROR_MESSAGE = 'An error occurred while merging'
    LEASE_TIMEOUT = 15.minutes.to_i

    delegate :merge_jid, :state, to: :@merge_request

    def execute(merge_request, options = {})
      return if merge_request.merged?
      return unless exclusive_lease(merge_request.id).try_obtain

      merge_strategy_class = options[:merge_strategy] || MergeRequests::MergeStrategies::FromSourceBranch
      @merge_strategy = merge_strategy_class.new(merge_request, current_user, merge_params: params, options: options)

      @merge_request = merge_request
      @options = options
      jid = merge_jid

      validate!

      merge_request.in_locked_state do
        if commit
          after_merge
          clean_merge_jid
          success
        end
      end

      log_info("Merge process finished on JID #{jid} with state #{state}")
    rescue MergeError, MergeRequests::MergeStrategies::StrategyError => e
      handle_merge_error(log_message: e.message, save_message_on_model: true)
    ensure
      exclusive_lease(merge_request.id).cancel
    end

    private

    def validate!
      authorization_check!
      error_check!
      validate_strategy!
      updated_check!
    end

    def authorization_check!
      unless @merge_request.can_be_merged_by?(current_user)
        raise_error('You are not allowed to merge this merge request')
      end
    end

    def validate_strategy!
      @merge_strategy.validate!
    end

    def updated_check!
      unless source_matches?
        raise_error('Branch has been updated since the merge was requested. '\
                    'Please review the changes.')
      end
    end

    def commit
      log_info("Git merge started on JID #{merge_jid}")

      merge_result = try_merge { @merge_strategy.execute_git_merge! }

      commit_sha = merge_result[:commit_sha]
      raise_error(GENERIC_ERROR_MESSAGE) unless commit_sha

      log_info("Git merge finished on JID #{merge_jid} commit #{commit_sha}")

      new_merge_request_attributes = {
        merged_commit_sha: commit_sha,
        merge_commit_sha: merge_result[:merge_commit_sha],
        squash_commit_sha: merge_result[:squash_commit_sha]
      }.compact
      merge_request.update!(new_merge_request_attributes) if new_merge_request_attributes.present?

      commit_sha
    ensure
      merge_request.update_and_mark_in_progress_merge_commit_sha(nil)
      log_info("Merge request marked in progress")
    end

    def try_merge
      yield
    rescue Gitlab::Git::PreReceiveError => e
      raise MergeError, "Something went wrong during merge pre-receive hook. #{e.message}".strip
    rescue StandardError => e
      handle_merge_error(log_message: e.message)
      raise_error(GENERIC_ERROR_MESSAGE)
    end

    def after_merge
      log_info("Post merge started on JID #{merge_jid} with state #{state}")
      MergeRequests::PostMergeService.new(project: project, current_user: current_user).execute(merge_request)
      log_info("Post merge finished on JID #{merge_jid} with state #{state}")

      if delete_source_branch?
        MergeRequests::DeleteSourceBranchWorker.perform_async(@merge_request.id, @merge_request.source_branch_sha, branch_deletion_user.id)
      end

      merge_request_merge_param
    end

    def clean_merge_jid
      merge_request.update_column(:merge_jid, nil)
    end

    def branch_deletion_user
      @merge_request.force_remove_source_branch? ? @merge_request.author : current_user
    end

    # Verify again that the source branch can be removed, since branch may be protected,
    # or the source branch may have been updated, or the user may not have permission
    #
    def delete_source_branch?
      params.fetch('should_remove_source_branch', @merge_request.force_remove_source_branch?) &&
        @merge_request.can_remove_source_branch?(branch_deletion_user)
    end

    def merge_request_merge_param
      if @merge_request.can_remove_source_branch?(branch_deletion_user) && !params.fetch('should_remove_source_branch', nil).nil?
        @merge_request.update(merge_params: @merge_request.merge_params.merge('should_remove_source_branch' => params['should_remove_source_branch']))
      end
    end

    def handle_merge_error(log_message:, save_message_on_model: false)
      log_error("MergeService ERROR: #{merge_request_info}:#{merge_status} - #{log_message}")
      @merge_request.update(merge_error: log_message) if save_message_on_model
    end

    def log_info(message)
      payload = log_payload("#{merge_request_info} - #{message}")
      logger.info(**payload)
    end

    def log_error(message)
      payload = log_payload(message)
      logger.error(**payload)
    end

    def logger
      @logger ||= Gitlab::AppLogger
    end

    def log_payload(message)
      Gitlab::ApplicationContext.current.merge(merge_request_info: merge_request_info, message: message)
    end

    def merge_request_info
      @merge_request_info ||= merge_request.to_reference(full: true)
    end

    def merge_status
      @merge_status ||= @merge_request.merge_status
    end

    def source_matches?
      # params-keys are symbols coming from the controller, but when they get
      # loaded from the database they're strings
      params.with_indifferent_access[:sha] == merge_request.diff_head_sha
    end

    def exclusive_lease(merge_request_id)
      strong_memoize(:"exclusive_lease_#{merge_request_id}") do
        lease_key = ['merge_requests_merge_service', merge_request_id].join(':')

        Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
      end
    end
  end
end

MergeRequests::MergeService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workflow do
  include ExclusiveLeaseHelpers

  let_it_be(:user) { create(:user) }
  let_it_be(:user2) { create(:user) }

  let(:merge_request) { create(:merge_request, :simple, author: user2, assignees: [user2]) }
  let(:project) { merge_request.project }

  before do
    project.add_maintainer(user)
    project.add_developer(user2)
  end

  describe '#execute' do
    let(:service) { described_class.new(project: project, current_user: user, params: merge_params) }
    let(:merge_params) do
      { commit_message: 'Awesome message', sha: merge_request.diff_head_sha }
    end

    let(:lease_key) { "merge_requests_merge_service:#{merge_request.id}" }
    let!(:lease) { stub_exclusive_lease(lease_key) }

    shared_examples 'with valid params' do
      before do
        allow(service).to receive(:execute_hooks)
        expect(merge_request).to receive(:update_and_mark_in_progress_merge_commit_sha).twice.and_call_original

        perform_enqueued_jobs do
          service.execute(merge_request)
        end
      end

      it { expect(merge_request).to be_valid }
      it { expect(merge_request).to be_merged }

      it 'does not update squash_commit_sha if it is not a squash' do
        expect(merge_request.squash_commit_sha).to be_nil
      end

      it 'sends email to user2 about merge of new merge_request' do
        email = ActionMailer::Base.deliveries.last
        expect(email.to.first).to eq(user2.email)
        expect(email.subject).to include(merge_request.title)
      end

      context 'note creation' do
        it 'creates resource state event about merge_request merge' do
          event = merge_request.resource_state_events.last
          expect(event.state).to eq('merged')
        end
      end
    end

    shared_examples 'squashing' do
      # A merge request with 5 commits
      let(:merge_request) do
        create(
          :merge_request,
          :simple,
          author: user2,
          assignees: [user2],
          squash: true,
          source_branch: 'improve/awesome',
          target_branch: 'fix'
        )
      end

      let(:merge_params) do
        { commit_message: 'Merge commit message',
          squash_commit_message: 'Squash commit message',
          sha: merge_request.diff_head_sha }
      end

      before do
        allow(service).to receive(:execute_hooks)
        expect(merge_request).to receive(:update_and_mark_in_progress_merge_commit_sha).twice.and_call_original

        perform_enqueued_jobs do
          service.execute(merge_request)
        end
      end

      it 'merges the merge request with squashed commits' do
        expect(merge_request).to be_merged

        merge_commit = merge_request.merge_commit
        squash_commit = merge_request.merge_commit.parents.last

        expect(merge_commit.message).to eq('Merge commit message')
        expect(squash_commit.message).to eq("Squash commit message\n")
      end

      it 'persists squash_commit_sha' do
        squash_commit = merge_request.merge_commit.parents.last

        expect(merge_request.squash_commit_sha).to eq(squash_commit.id)
      end
    end

    context 'when merge strategy is merge commit' do
      it 'persists merge_commit_sha and merged_commit_sha and nullifies in_progress_merge_commit_sha' do
        service.execute(merge_request)

        expect(merge_request.merge_commit_sha).not_to be_nil
        expect(merge_request.merged_commit_sha).to eq merge_request.merge_commit_sha
        expect(merge_request.in_progress_merge_commit_sha).to be_nil
      end

      it_behaves_like 'with valid params'

      it_behaves_like 'squashing'
    end

    context 'when merge strategy is fast forward' do
      before do
        project.update!(merge_requests_ff_only_enabled: true)
      end

      let(:merge_request) do
        create(
          :merge_request,
          source_branch: 'flatten-dir',
          target_branch: 'improve/awesome',
          assignees: [user2],
          author: create(:user)
        )
      end

      it 'does not create merge_commit_sha, but persists merged_commit_sha and nullifies in_progress_merge_commit_sha' do
        service.execute(merge_request)

        expect(merge_request.merge_commit_sha).to be_nil
        expect(merge_request.merged_commit_sha).not_to be_nil
        expect(merge_request.merged_commit_sha).to eq merge_request.diff_head_sha
        expect(merge_request.in_progress_merge_commit_sha).to be_nil
      end

      it_behaves_like 'with valid params'

      it 'updates squash_commit_sha and merged_commit_sha if it is a squash' do
        expect(merge_request).to receive(:update_and_mark_in_progress_merge_commit_sha).twice.and_call_original

        merge_request.update!(squash: true)

        expect { service.execute(merge_request) }
          .to change { merge_request.squash_commit_sha }
                .from(nil)

        expect(merge_request.merge_commit_sha).to be_nil
        expect(merge_request.merged_commit_sha).to eq merge_request.squash_commit_sha
        expect(merge_request.in_progress_merge_commit_sha).to be_nil
      end
    end

    context 'running the service once' do
      let(:ref) { merge_request.to_reference(full: true) }
      let(:jid) { SecureRandom.hex }

      let(:messages) do
        [
          /#{ref} - Git merge started on JID #{jid}/,
          /#{ref} - Git merge finished on JID #{jid}/,
          /#{ref} - Post merge started on JID #{jid}/,
          /#{ref} - Post merge finished on JID #{jid}/,
          /#{ref} - Merge process finished on JID #{jid}/
        ]
      end

      before do
        merge_request.update!(merge_jid: jid)
        ::Gitlab::ApplicationContext.push(caller_id: 'MergeWorker')
      end

      it 'logs status messages' do
        allow(Gitlab::AppLogger).to receive(:info).and_call_original

        messages.each do |message|
          expect(Gitlab::AppLogger).to receive(:info).with(
            hash_including(
              'meta.caller_id' => 'MergeWorker',
              message: message,
              merge_request_info: ref
            )
          ).and_call_original
        end

        service.execute(merge_request)
      end
    end

    context 'running the service multiple time' do
      it 'is idempotent' do
        2.times { service.execute(merge_request) }

        expect(merge_request.merge_error).to be_falsey
        expect(merge_request).to be_valid
        expect(merge_request).to be_merged

        commit_messages = project.repository.commits('master', limit: 2).map(&:message)
        expect(commit_messages.uniq.size).to eq(2)
        expect(merge_request.in_progress_merge_commit_sha).to be_nil
      end
    end

    context 'when an invalid sha is passed' do
      let(:merge_request) do
        create(
          :merge_request,
          :simple,
          author: user2,
          assignees: [user2],
          squash: true,
          source_branch: 'improve/awesome',
          target_branch: 'fix'
        )
      end

      let(:merge_params) do
        { sha: merge_request.commits.second.sha }
      end

      it 'does not merge the MR' do
        service.execute(merge_request)

        expect(merge_request).not_to be_merged
        expect(merge_request.merge_error).to match(/Branch has been updated/)
      end
    end

    context 'when the `sha` param is missing' do
      let(:merge_params) { {} }

      it 'returns the error' do
        merge_error = 'Branch has been updated since the merge was requested. '\
                      'Please review the changes.'

        expect { service.execute(merge_request) }
          .to change { merge_request.merge_error }
                .from(nil).to(merge_error)
      end
    end

    context 'closes related issues' do
      before do
        allow(project).to receive(:default_branch).and_return(merge_request.target_branch)
      end

      it 'closes GitLab issue tracker issues', :sidekiq_inline do
        issue  = create :issue, project: project
        commit = double('commit', safe_message: "Fixes #{issue.to_reference}", date: Time.current, authored_date: Time.current)
        allow(merge_request).to receive(:commits).and_return([commit])
        merge_request.cache_merge_request_closes_issues!

        service.execute(merge_request)

        expect(issue.reload.closed?).to be_truthy
      end

      context 'with Jira integration' do
        include JiraIntegrationHelpers

        let(:jira_tracker) { project.create_jira_integration }
        let(:jira_issue)   { ExternalIssue.new('JIRA-123', project) }
        let(:commit)       { double('commit', safe_message: "Fixes #{jira_issue.to_reference}") }

        before do
          stub_jira_integration_test
          project.update!(has_external_issue_tracker: true)
          jira_integration_settings
          stub_jira_urls(jira_issue.id)
          allow(merge_request).to receive(:commits).and_return([commit])
        end

        it 'closes issues on Jira issue tracker' do
          jira_issue = ExternalIssue.new('JIRA-123', project)
          stub_jira_urls(jira_issue)
          commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}")
          allow(merge_request).to receive(:commits).and_return([commit])

          expect_any_instance_of(Integrations::Jira).to receive(:close_issue).with(merge_request, jira_issue, user).once

          service.execute(merge_request)
        end

        context 'wrong issue markdown' do
          it 'does not close issues on Jira issue tracker' do
            jira_issue = ExternalIssue.new('#JIRA-123', project)
            stub_jira_urls(jira_issue)
            commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}")
            allow(merge_request).to receive(:commits).and_return([commit])

            expect_any_instance_of(Integrations::Jira).not_to receive(:close_issue)

            service.execute(merge_request)
          end
        end
      end
    end

    context 'closes related todos' do
      let(:merge_request) { create(:merge_request, assignees: [user], author: user) }
      let(:project) { merge_request.project }

      let!(:todo) do
        create(:todo, :assigned,
          project: project,
          author: user,
          user: user,
          target: merge_request)
      end

      before do
        allow(service).to receive(:execute_hooks)

        perform_enqueued_jobs do
          service.execute(merge_request)
          todo.reload
        end
      end

      it { expect(todo).to be_done }
    end

    context 'source branch removal' do
      context 'when the source branch is protected' do
        let(:service) do
          described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => true))
        end

        before do
          create(:protected_branch, project: project, name: merge_request.source_branch)
        end

        it 'does not delete the source branch' do
          expect(::Branches::DeleteService).not_to receive(:new)

          service.execute(merge_request)
        end
      end

      context 'when the source branch is the default branch' do
        let(:service) do
          described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => true))
        end

        before do
          allow(project).to receive(:root_ref?).with(merge_request.source_branch).and_return(true)
        end

        it 'does not delete the source branch' do
          expect(::Branches::DeleteService).not_to receive(:new)
          service.execute(merge_request)
        end
      end

      context 'when the source branch can be removed' do
        context 'when MR author set the source branch to be removed' do
          before do
            merge_request.update_attribute(:merge_params, { 'force_remove_source_branch' => '1' })
          end

          # Not a real use case. When a merger merges a MR , merge param 'should_remove_source_branch' is defined
          it 'removes the source branch using the author user' do
            expect(::MergeRequests::DeleteSourceBranchWorker).to receive(:perform_async).with(merge_request.id, merge_request.source_branch_sha, merge_request.author.id)

            service.execute(merge_request)

            expect(merge_request.reload.should_remove_source_branch?).to be nil
          end

          context 'when the merger set the source branch not to be removed' do
            let(:service) { described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => false)) }

            it 'does not delete the source branch' do
              expect(::MergeRequests::DeleteSourceBranchWorker).not_to receive(:perform_async)

              service.execute(merge_request)

              expect(merge_request.reload.should_remove_source_branch?).to be false
            end
          end
        end

        context 'when MR merger set the source branch to be removed' do
          let(:service) do
            described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => true))
          end

          it 'removes the source branch using the current user' do
            expect(::MergeRequests::DeleteSourceBranchWorker).to receive(:perform_async).with(merge_request.id, merge_request.source_branch_sha, user.id)

            service.execute(merge_request)

            expect(merge_request.reload.should_remove_source_branch?).to be true
          end
        end
      end
    end

    context 'error handling' do
      before do
        allow(Gitlab::AppLogger).to receive(:error)
      end

      context 'when source is missing' do
        it 'logs and saves error' do
          allow(merge_request).to receive(:diff_head_sha) { nil }

          error_message = 'No source for merge'

          service.execute(merge_request)

          expect(merge_request.merge_error).to eq(error_message)
          expect(Gitlab::AppLogger).to have_received(:error).with(
            hash_including(
              merge_request_info: merge_request.to_reference(full: true),
              message: a_string_matching(error_message)
            )
          )
        end
      end

      it 'logs and saves error if there is an exception' do
        error_message = 'error message'

        allow_next_instance_of(MergeRequests::MergeStrategies::FromSourceBranch) do |strategy|
          allow(strategy).to receive(:execute_git_merge!).and_raise(error_message)
        end

        service.execute(merge_request)

        expect(merge_request.merge_error).to eq(described_class::GENERIC_ERROR_MESSAGE)
        expect(Gitlab::AppLogger).to have_received(:error).with(
          hash_including(
            merge_request_info: merge_request.to_reference(full: true),
            message: a_string_matching(error_message)
          )
        )
      end

      it 'logs and saves error if user is not authorized' do
        stub_exclusive_lease

        unauthorized_user = create(:user)
        project.add_reporter(unauthorized_user)

        service = described_class.new(project: project, current_user: unauthorized_user)

        service.execute(merge_request)

        expect(merge_request.merge_error)
          .to eq('You are not allowed to merge this merge request')
      end

      it 'logs and saves error if there is an PreReceiveError exception' do
        error_message = 'error message'

        allow_next_instance_of(MergeRequests::MergeStrategies::FromSourceBranch) do |strategy|
          allow(strategy).to receive(:execute_git_merge!).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}")
        end

        service.execute(merge_request)

        expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook')
        expect(Gitlab::AppLogger).to have_received(:error).with(
          hash_including(
            merge_request_info: merge_request.to_reference(full: true),
            message: a_string_matching(error_message)
          )
        )
      end

      it 'logs and saves error if commit is not created' do
        allow_any_instance_of(Repository).to receive(:merge).and_return(false)
        allow(service).to receive(:execute_hooks)

        service.execute(merge_request)

        expect(merge_request).to be_open
        expect(merge_request.merge_commit_sha).to be_nil
        expect(merge_request.merge_error).to include(described_class::GENERIC_ERROR_MESSAGE)
        expect(Gitlab::AppLogger).to have_received(:error).with(
          hash_including(
            merge_request_info: merge_request.to_reference(full: true),
            message: a_string_matching(described_class::GENERIC_ERROR_MESSAGE)
          )
        )
      end

      context 'when squashing is required' do
        before do
          merge_request.update!(source_branch: 'master', target_branch: 'feature')
          merge_request.target_project.project_setting.squash_always!
        end

        it 'raises an error if squashing is not done' do
          error_message = 'requires squashing commits'

          service.execute(merge_request)

          expect(merge_request).to be_open

          expect(merge_request.merge_commit_sha).to be_nil
          expect(merge_request.squash_commit_sha).to be_nil
          expect(merge_request.merge_error).to include(error_message)
          expect(Gitlab::AppLogger).to have_received(:error).with(
            hash_including(
              merge_request_info: merge_request.to_reference(full: true),
              message: a_string_matching(error_message)
            )
          )
        end
      end

      context 'when squashing' do
        before do
          merge_request.update!(source_branch: 'master', target_branch: 'feature')
        end

        it 'logs and saves error if there is an error when squashing' do
          error_message = 'Squashing failed: Squash the commits locally, resolve any conflicts, then push the branch.'

          allow_any_instance_of(MergeRequests::SquashService).to receive(:squash!).and_return(nil)
          merge_request.update!(squash: true)

          service.execute(merge_request)

          expect(merge_request).to be_open
          expect(merge_request.merge_commit_sha).to be_nil
          expect(merge_request.squash_commit_sha).to be_nil
          expect(merge_request.merge_error).to include(error_message)
          expect(Gitlab::AppLogger).to have_received(:error).with(
            hash_including(
              merge_request_info: merge_request.to_reference(full: true),
              message: a_string_matching(error_message)
            )
          )
        end

        it 'logs and saves error if there is an PreReceiveError exception' do
          error_message = 'error message'

          allow_next_instance_of(MergeRequests::MergeStrategies::FromSourceBranch) do |strategy|
            allow(strategy).to receive(:execute_git_merge!).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}")
          end
          merge_request.update!(squash: true)

          service.execute(merge_request)

          expect(merge_request).to be_open
          expect(merge_request.merge_commit_sha).to be_nil
          expect(merge_request.squash_commit_sha).to be_nil
          expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook')
          expect(Gitlab::AppLogger).to have_received(:error).with(
            hash_including(
              merge_request_info: merge_request.to_reference(full: true),
              message: a_string_matching(error_message)
            )
          )
        end

        context 'when fast-forward merge is not allowed' do
          before do
            allow_any_instance_of(Repository).to receive(:ancestor?).and_return(nil)
          end

          %w[semi-linear ff].each do |merge_method|
            it "logs and saves error if merge is #{merge_method} only" do
              merge_method = 'rebase_merge' if merge_method == 'semi-linear'
              merge_request.project.update!(merge_method: merge_method)
              error_message = 'Only fast-forward merge is allowed for your project. Please update your source branch'
              allow(service).to receive(:execute_hooks)
              expect(lease).to receive(:cancel)

              service.execute(merge_request)

              expect(merge_request).to be_open
              expect(merge_request.merge_commit_sha).to be_nil
              expect(merge_request.squash_commit_sha).to be_nil
              expect(merge_request.merge_error).to include(error_message)
              expect(Gitlab::AppLogger).to have_received(:error).with(
                hash_including(
                  merge_request_info: merge_request.to_reference(full: true),
                  message: a_string_matching(error_message)
                )
              )
            end
          end
        end
      end

      context 'when not mergeable' do
        let!(:error_message) { 'Merge request is not mergeable' }

        context 'with failing CI' do
          before do
            allow(merge_request.project).to receive(:only_allow_merge_if_pipeline_succeeds) { true }
            allow(merge_request).to receive(:mergeable_ci_state?) { false }
          end

          it 'logs and saves error' do
            service.execute(merge_request)

            expect(Gitlab::AppLogger).to have_received(:error).with(
              hash_including(
                merge_request_info: merge_request.to_reference(full: true),
                message: a_string_matching(error_message)
              )
            )
          end
        end

        context 'with unresolved discussions' do
          before do
            allow(merge_request.project).to receive(:only_allow_merge_if_all_discussions_are_resolved) { true }
            allow(merge_request).to receive(:mergeable_discussions_state?) { false }
          end

          it 'logs and saves error' do
            service.execute(merge_request)

            expect(Gitlab::AppLogger).to have_received(:error).with(
              hash_including(
                merge_request_info: merge_request.to_reference(full: true),
                message: a_string_matching(error_message)
              )
            )
          end

          context 'when passing `skip_discussions_check: true` as `options` parameter' do
            it 'merges the merge request' do
              service.execute(merge_request, skip_discussions_check: true)

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

      context 'when passing `check_mergeability_retry_lease: true` as `options` parameter' do
        it 'call mergeable? with check_mergeability_retry_lease' do
          expect(merge_request).to receive(:mergeable?).with(hash_including(check_mergeability_retry_lease: true)).and_call_original

          service.execute(merge_request, check_mergeability_retry_lease: true)
        end
      end
    end

    context 'when the other sidekiq worker has already been running' do
      before do
        stub_exclusive_lease_taken(lease_key)
      end

      it 'does not execute service' do
        expect(service).not_to receive(:commit)

        service.execute(merge_request)
      end
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true
module MergeRequests
  module Mergeability
    class RunChecksService
      include Gitlab::Utils::StrongMemoize

      def initialize(merge_request:, params:)
        @merge_request = merge_request
        @params = params
      end

      def execute(checks, execute_all: false)
        @results = checks.each_with_object([]) do |check_class, result_hash|
          check = check_class.new(merge_request: merge_request, params: params)

          next if check.skip?

          check_result = logger.instrument(mergeability_name: check_class.to_s.demodulize.underscore) do
            run_check(check)
          end

          result_hash << check_result

          break result_hash if check_result.failed? && !execute_all
        end

        logger.commit

        return ServiceResponse.success(payload: { results: results }) if all_results_success?

        ServiceResponse.error(
          message: 'Checks failed.',
          payload: {
            results: results,
            failed_check: failed_check
          }
        )
      end

      private

      attr_reader :merge_request, :params, :results

      def run_check(check)
        return check.execute unless check.cacheable?

        cached_result = cached_results.read(merge_check: check)
        return cached_result if cached_result.respond_to?(:status)

        check.execute.tap do |result|
          cached_results.write(merge_check: check, result_hash: result.to_hash)
        end
      end

      def cached_results
        strong_memoize(:cached_results) do
          Gitlab::MergeRequests::Mergeability::ResultsStore.new(merge_request: merge_request)
        end
      end

      def logger
        strong_memoize(:logger) do
          MergeRequests::Mergeability::Logger.new(merge_request: merge_request)
        end
      end

      def all_results_success?
        results.none?(&:failed?)
      end

      def failed_check
        # NOTE: the identifier could be string when we retrieve it from the cache
        # so let's make sure we always return symbols here.
        results.find(&:failed?)&.payload&.fetch(:identifier)&.to_sym
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::RunChecksService, :clean_gitlab_redis_cache, feature_category: :code_review_workflow do
  let(:checks) { MergeRequest.all_mergeability_checks }
  let(:execute_all) { false }

  subject(:run_checks) { described_class.new(merge_request: merge_request, params: {}) }

  describe '#execute' do
    subject(:execute) { run_checks.execute(checks, execute_all: execute_all) }

    let_it_be(:merge_request) { create(:merge_request) }

    let(:params) { {} }
    let(:success_result) { Gitlab::MergeRequests::Mergeability::CheckResult.success }

    shared_examples 'checks are all executed' do
      context 'when all checks are set to be executed' do
        let(:execute_all) { true }

        specify do
          result = execute

          expect(result.success?).to eq(success?)
          expect(result.payload[:results].count).to eq(expected_count)
        end
      end
    end

    context 'when every check is skipped', :eager_load do
      before do
        MergeRequests::Mergeability::CheckBaseService.subclasses.each do |subclass|
          allow_next_instance_of(subclass) do |service|
            allow(service).to receive(:skip?).and_return(true)
          end
        end
      end

      it 'is still a success' do
        expect(execute.success?).to eq(true)
      end

      it_behaves_like 'checks are all executed' do
        let(:success?) { true }
        let(:expected_count) { 0 }
      end
    end

    context 'when a check is skipped' do
      before do
        checks.each do |check|
          allow_next_instance_of(check) do |service|
            allow(service).to receive(:skip?).and_return(false)
            allow(service).to receive(:execute).and_return(success_result)
          end
        end

        allow_next_instance_of(MergeRequests::Mergeability::CheckCiStatusService) do |service|
          allow(service).to receive(:skip?).and_return(true)
        end
      end

      it 'does not execute the check' do
        expect_next_instance_of(MergeRequests::Mergeability::CheckCiStatusService) do |service|
          expect(service).to receive(:skip?).and_return(true)
          expect(service).not_to receive(:execute)
        end

        expect(execute.success?).to eq(true)
      end

      it_behaves_like 'checks are all executed' do
        let(:success?) { true }
        let(:expected_count) { checks.count - 1 }
      end

      context 'when one check fails' do
        let(:failed_result) { Gitlab::MergeRequests::Mergeability::CheckResult.failed(payload: { identifier: 'failed' }) }

        before do
          allow_next_instance_of(MergeRequests::Mergeability::CheckOpenStatusService) do |service|
            allow(service).to receive(:skip?).and_return(false)
            allow(service).to receive(:execute).and_return(failed_result)
          end
        end

        it 'returns the failed check' do
          result = execute

          expect(result.success?).to eq(false)
          expect(execute.payload[:failed_check]).to eq(:failed)
        end

        it_behaves_like 'checks are all executed' do
          let(:success?) { false }
          let(:expected_count) { checks.count - 1 }
        end
      end

      context 'when one check is inactive' do
        let(:inactive_result) { Gitlab::MergeRequests::Mergeability::CheckResult.inactive }

        before do
          allow_next_instance_of(MergeRequests::Mergeability::CheckOpenStatusService) do |service|
            allow(service).to receive(:skip?).and_return(false)
            allow(service).to receive(:execute).and_return(inactive_result)
          end
        end

        it 'is still a success' do
          expect(execute.success?).to eq(true)
        end

        it_behaves_like 'checks are all executed' do
          let(:success?) { true }
          let(:expected_count) { checks.count - 1 }
        end
      end
    end

    context 'when a check is not skipped' do
      let(:cacheable) { true }
      let(:merge_check) { instance_double(MergeRequests::Mergeability::CheckCiStatusService) }

      before do
        checks.each do |check|
          allow_next_instance_of(check) do |service|
            allow(service).to receive(:skip?).and_return(true)
          end
        end

        expect(MergeRequests::Mergeability::CheckCiStatusService).to receive(:new).and_return(merge_check)
        expect(merge_check).to receive(:skip?).and_return(false)
        allow(merge_check).to receive(:cacheable?).and_return(cacheable)
        allow(merge_check).to receive(:execute).and_return(success_result)
      end

      context 'when the check is cacheable' do
        context 'when the check is cached' do
          before do
            expect_next_instance_of(Gitlab::MergeRequests::Mergeability::ResultsStore) do |service|
              expect(service).to receive(:read).with(merge_check: merge_check).and_return(success_result)
            end
          end

          it 'returns the cached result' do
            expect_next_instance_of(MergeRequests::Mergeability::Logger, merge_request: merge_request) do |logger|
              expect(logger).to receive(:instrument).with(mergeability_name: 'check_ci_status_service').and_call_original
              expect(logger).to receive(:commit)
            end

            expect(execute.success?).to eq(true)
          end

          it_behaves_like 'checks are all executed' do
            let(:success?) { true }
            let(:expected_count) { 1 }
          end
        end

        context 'when the check is not cached' do
          before do
            expect_next_instance_of(Gitlab::MergeRequests::Mergeability::ResultsStore) do |service|
              expect(service).to receive(:read).with(merge_check: merge_check).and_return(nil)
              expect(service).to receive(:write).with(merge_check: merge_check, result_hash: success_result.to_hash).and_return(true)
            end
          end

          it 'writes and returns the result' do
            expect_next_instance_of(MergeRequests::Mergeability::Logger, merge_request: merge_request) do |logger|
              expect(logger).to receive(:instrument).with(mergeability_name: 'check_ci_status_service').and_call_original
              expect(logger).to receive(:commit)
            end

            expect(execute.success?).to eq(true)
          end

          it_behaves_like 'checks are all executed' do
            let(:success?) { true }
            let(:expected_count) { 1 }
          end
        end
      end

      context 'when check is not cacheable' do
        let(:cacheable) { false }

        it 'does not call the results store' do
          expect(Gitlab::MergeRequests::Mergeability::ResultsStore).not_to receive(:new)

          expect(execute.success?).to eq(true)
        end
      end
    end
  end
end


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

module MergeRequests
  module Mergeability
    class DetailedMergeStatusService
      include ::Gitlab::Utils::StrongMemoize

      def initialize(merge_request:)
        @merge_request = merge_request
      end

      def execute
        return :preparing if preparing?
        return :checking if checking?
        return :unchecked if unchecked?

        if check_results.success?

          # If everything else is mergeable, but CI is not, the frontend expects two potential states to be returned
          # See discussion: gitlab.com/gitlab-org/gitlab/-/merge_requests/96778#note_1093063523
          if check_ci_results.failed?
            ci_check_failed_check
          else
            :mergeable
          end
        else
          check_results.payload[:failed_check]
        end
      end

      private

      attr_reader :merge_request, :checks, :ci_check

      def preparing?
        merge_request.preparing? && !merge_request.merge_request_diff.persisted?
      end

      def checking?
        merge_request.cannot_be_merged_rechecking? || merge_request.checking?
      end

      def unchecked?
        merge_request.unchecked?
      end

      def check_results
        strong_memoize(:check_results) do
          merge_request
            .execute_merge_checks(
              MergeRequest.mergeable_state_checks,
              params: { skip_ci_check: true }
            )
        end
      end

      def check_ci_results
        strong_memoize(:check_ci_results) do
          ::MergeRequests::Mergeability::CheckCiStatusService.new(merge_request: merge_request, params: {}).execute
        end
      end

      def ci_check_failed_check
        if merge_request.actual_head_pipeline&.active?
          :ci_still_running
        else
          check_ci_results.payload.fetch(:identifier)
        end
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::MergeRequests::Mergeability::DetailedMergeStatusService, feature_category: :code_review_workflow do
  subject(:detailed_merge_status) { described_class.new(merge_request: merge_request).execute }

  context 'when merge status is cannot_be_merged_rechecking' do
    let(:merge_request) { create(:merge_request, merge_status: :cannot_be_merged_rechecking) }

    it 'returns :checking' do
      expect(detailed_merge_status).to eq(:checking)
    end
  end

  context 'when merge status is preparing' do
    let(:merge_request) { create(:merge_request, merge_status: :preparing) }

    it 'returns :checking' do
      allow(merge_request.merge_request_diff).to receive(:persisted?).and_return(false)

      expect(detailed_merge_status).to eq(:preparing)
    end
  end

  context 'when merge status is preparing and merge request diff is persisted' do
    let(:merge_request) { create(:merge_request, merge_status: :preparing) }

    it 'returns :checking' do
      allow(merge_request.merge_request_diff).to receive(:persisted?).and_return(true)

      expect(detailed_merge_status).to eq(:mergeable)
    end
  end

  context 'when merge status is checking' do
    let(:merge_request) { create(:merge_request, merge_status: :checking) }

    it 'returns :checking' do
      expect(detailed_merge_status).to eq(:checking)
    end
  end

  context 'when merge status is unchecked' do
    let(:merge_request) { create(:merge_request, merge_status: :unchecked) }

    it 'returns :unchecked' do
      expect(detailed_merge_status).to eq(:unchecked)
    end
  end

  context 'when merge checks are a success' do
    let(:merge_request) { create(:merge_request) }

    it 'returns :mergeable' do
      expect(detailed_merge_status).to eq(:mergeable)
    end
  end

  context 'when merge status have a failure' do
    let(:merge_request) { create(:merge_request) }

    before do
      merge_request.close!
    end

    it 'returns the failed check' do
      expect(detailed_merge_status).to eq(:not_open)
    end
  end

  context 'when all but the ci check fails' do
    let(:merge_request) { create(:merge_request) }

    before do
      merge_request.project.update!(only_allow_merge_if_pipeline_succeeds: true)
    end

    context 'when pipeline does not exist' do
      it 'returns the failed check' do
        expect(detailed_merge_status).to eq(:ci_must_pass)
      end
    end

    context 'when pipeline exists' do
      before do
        create(
          :ci_pipeline,
          ci_status,
          merge_request: merge_request,
          project: merge_request.project,
          sha: merge_request.source_branch_sha,
          head_pipeline_of: merge_request
        )
      end

      context 'when the pipeline is running' do
        let(:ci_status) { :running }

        it 'returns the failed check' do
          expect(detailed_merge_status).to eq(:ci_still_running)
        end
      end

      context 'when the pipeline is pending' do
        let(:ci_status) { :pending }

        it { expect(detailed_merge_status).to eq(:ci_still_running) }
      end

      context 'when the pipeline is not running' do
        let(:ci_status) { :failed }

        it 'returns the failed check' do
          expect(detailed_merge_status).to eq(:ci_must_pass)
        end
      end
    end
  end
end


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

module MergeRequests
  module Mergeability
    class CheckDraftStatusService < CheckBaseService
      identifier :draft_status
      description 'Checks whether the merge request is draft'

      def execute
        if merge_request.draft?
          failure
        else
          success
        end
      end

      def skip?
        params[:skip_draft_check].present?
      end

      def cacheable?
        false
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::CheckDraftStatusService, feature_category: :code_review_workflow do
  subject(:check_draft_status) { described_class.new(merge_request: merge_request, params: params) }

  let_it_be(:merge_request) { build(:merge_request) }

  let(:params) { { skip_draft_check: skip_check } }
  let(:skip_check) { false }

  it_behaves_like 'mergeability check service', :draft_status, 'Checks whether the merge request is draft'

  describe '#execute' do
    let(:result) { check_draft_status.execute }

    before do
      expect(merge_request).to receive(:draft?).and_return(draft)
    end

    context 'when the merge request is a draft' do
      let(:draft) { true }

      it 'returns a check result with status failed' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
        expect(result.payload[:identifier]).to eq(:draft_status)
      end
    end

    context 'when the merge request is not a draft' do
      let(:draft) { false }

      it 'returns a check result with status success' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
      end
    end
  end

  describe '#skip?' do
    context 'when skip check param is true' do
      let(:skip_check) { true }

      it 'returns true' do
        expect(check_draft_status.skip?).to eq true
      end
    end

    context 'when skip check param is false' do
      let(:skip_check) { false }

      it 'returns false' do
        expect(check_draft_status.skip?).to eq false
      end
    end
  end

  describe '#cacheable?' do
    it 'returns false' do
      expect(check_draft_status.cacheable?).to eq false
    end
  end
end


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

module MergeRequests
  module Mergeability
    class CheckRebaseStatusService < CheckBaseService
      identifier :need_rebase
      description 'Checks whether the merge request needs to be rebased'

      def execute
        return inactive unless merge_request.project.ff_merge_must_be_possible?

        if merge_request.should_be_rebased?
          failure
        else
          success
        end
      end

      def skip?
        params[:skip_rebase_check].present?
      end

      def cacheable?
        false
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::CheckRebaseStatusService, feature_category: :code_review_workflow do
  subject(:check_rebase_status) { described_class.new(merge_request: merge_request, params: params) }

  let_it_be(:project) { build(:project) }
  let_it_be(:merge_request) { build(:merge_request, source_project: project) }
  let(:params) { { skip_rebase_check: skip_check } }
  let(:skip_check) { false }

  it_behaves_like 'mergeability check service', :need_rebase, 'Checks whether the merge request needs to be rebased'

  describe '#execute' do
    let(:result) { check_rebase_status.execute }

    before do
      allow(project)
        .to receive(:ff_merge_must_be_possible?)
        .and_return(ff_merge_must_be_possible?)
    end

    context 'when ff_merge_must_be_possible is true' do
      let(:ff_merge_must_be_possible?) { true }

      before do
        allow(merge_request).to receive(:should_be_rebased?).and_return(should_be_rebased)
      end

      context 'when the merge request should be rebased' do
        let(:should_be_rebased) { true }

        it 'returns a check result with status failed' do
          expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
          expect(result.payload[:identifier]).to eq(:need_rebase)
        end
      end

      context 'when the merge request should not be rebased' do
        let(:should_be_rebased) { false }

        it 'returns a check result with status success' do
          expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
        end
      end
    end

    context 'when ff_merge_must_be_possible is false' do
      let(:ff_merge_must_be_possible?) { false }

      it 'returns a check result with inactive status' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::INACTIVE_STATUS
      end
    end
  end

  describe '#skip?' do
    context 'when skip check is true' do
      let(:skip_check) { true }

      it 'returns true' do
        expect(check_rebase_status.skip?).to eq true
      end
    end

    context 'when skip check is false' do
      let(:skip_check) { false }

      it 'returns false' do
        expect(check_rebase_status.skip?).to eq false
      end
    end
  end

  describe '#cacheable?' do
    it 'returns false' do
      expect(check_rebase_status.cacheable?).to eq false
    end
  end
end


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

module MergeRequests
  module Mergeability
    class Logger
      include Gitlab::Utils::StrongMemoize

      def initialize(merge_request:, destination: Gitlab::AppJsonLogger)
        @destination = destination
        @merge_request = merge_request
      end

      def commit
        commit_logs
      end

      def instrument(mergeability_name:)
        raise ArgumentError, 'block not given' unless block_given?

        op_start_db_counters = current_db_counter_payload
        op_started_at = current_monotonic_time

        result = yield

        observe_result(mergeability_name, result)
        observe("mergeability.#{mergeability_name}.duration_s", current_monotonic_time - op_started_at)
        observe_sql_counters(mergeability_name, op_start_db_counters, current_db_counter_payload)

        result
      end

      private

      attr_reader :destination, :merge_request, :stored_result

      def observe_result(name, result)
        return unless result.respond_to?(:success?)

        observe("mergeability.#{name}.successful", result.success?)
      end

      def observe(name, value)
        observations[name.to_s].push(value)
      end

      def commit_logs
        attributes = Gitlab::ApplicationContext.current.merge({ mergeability_project_id: merge_request.project.id })

        attributes[:mergeability_merge_request_id] = merge_request.id
        attributes.merge!(observations_hash)
        attributes.compact!
        attributes.stringify_keys!

        destination.info(attributes)
      end

      def observations_hash
        transformed = observations.transform_values do |values|
          next if values.empty?

          {
            'values' => values
          }
        end.compact

        transformed.each_with_object({}) do |key, hash|
          key[1].each { |k, v| hash["#{key[0]}.#{k}"] = v }
        end
      end

      def observations
        strong_memoize(:observations) do
          Hash.new { |hash, key| hash[key] = [] }
        end
      end

      def observe_sql_counters(name, start_db_counters, end_db_counters)
        end_db_counters.each do |key, value|
          result = value - start_db_counters.fetch(key, 0)
          next if result == 0

          observe("mergeability.#{name}.#{key}", result)
        end
      end

      def current_db_counter_payload
        ::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_payload
      end

      def current_monotonic_time
        ::Gitlab::Metrics::System.monotonic_time
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::Logger, :request_store, feature_category: :code_review_workflow do
  let_it_be(:merge_request) { create(:merge_request) }

  subject(:logger) { described_class.new(merge_request: merge_request) }

  let(:caller_id) { 'a' }

  before do
    allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(:caller_id).and_return(caller_id)
  end

  def loggable_data(**extras)
    {
      'mergeability.expensive_operation.duration_s.values' => a_kind_of(Array),
      "mergeability_merge_request_id" => merge_request.id,
      "correlation_id" => a_kind_of(String),
      "mergeability_project_id" => merge_request.project.id
    }.merge(extras)
  end

  describe '#instrument' do
    let(:operation_count) { 1 }

    context 'when enabled' do
      it "returns the block's value" do
        expect(logger.instrument(mergeability_name: :expensive_operation) { 123 }).to eq(123)
      end

      it 'records durations of instrumented operations' do
        expect_next_instance_of(Gitlab::AppJsonLogger) do |app_logger|
          expect(app_logger).to receive(:info).with(match(a_hash_including(loggable_data)))
        end

        expect(logger.instrument(mergeability_name: :expensive_operation) { 123 }).to eq(123)

        logger.commit
      end

      context 'when block value responds to #success?' do
        let(:success?) { true }
        let(:check_result) { instance_double(Gitlab::MergeRequests::Mergeability::CheckResult, success?: success?) }

        let(:extra_data) do
          {
            'mergeability.expensive_operation.successful.values' => [success?]
          }
        end

        shared_examples_for 'success state logger' do
          it 'records operation success state' do
            expect_next_instance_of(Gitlab::AppJsonLogger) do |app_logger|
              expect(app_logger).to receive(:info).with(match(a_hash_including(loggable_data(**extra_data))))
            end

            expect(logger.instrument(mergeability_name: :expensive_operation) { check_result }).to eq(check_result)

            logger.commit
          end
        end

        it_behaves_like 'success state logger'

        context 'when not successful' do
          let(:success?) { false }

          it_behaves_like 'success state logger'
        end
      end

      context 'with multiple observations' do
        let(:operation_count) { 2 }

        it 'records durations of instrumented operations' do
          expect_next_instance_of(Gitlab::AppJsonLogger) do |app_logger|
            expect(app_logger).to receive(:info).with(match(a_hash_including(loggable_data)))
          end

          2.times do
            expect(logger.instrument(mergeability_name: :expensive_operation) { 123 }).to eq(123)
          end

          logger.commit
        end
      end

      context 'when its a query' do
        let(:extra_data) do
          {
            'mergeability.expensive_operation.db_count.values' => a_kind_of(Array),
            'mergeability.expensive_operation.db_main_count.values' => a_kind_of(Array),
            'mergeability.expensive_operation.db_main_duration_s.values' => a_kind_of(Array),
            'mergeability.expensive_operation.db_primary_count.values' => a_kind_of(Array),
            'mergeability.expensive_operation.db_primary_duration_s.values' => a_kind_of(Array)
          }
        end

        context 'with a single query' do
          it 'includes SQL metrics' do
            expect_next_instance_of(Gitlab::AppJsonLogger) do |app_logger|
              expect(app_logger).to receive(:info).with(match(a_hash_including(loggable_data(**extra_data))))
            end

            expect(logger.instrument(mergeability_name: :expensive_operation) { MergeRequest.count }).to eq(1)

            logger.commit
          end
        end

        context 'with multiple queries' do
          it 'includes SQL metrics' do
            expect_next_instance_of(Gitlab::AppJsonLogger) do |app_logger|
              expect(app_logger).to receive(:info).with(match(a_hash_including(loggable_data(**extra_data))))
            end

            expect(logger.instrument(mergeability_name: :expensive_operation) { Project.count + MergeRequest.count })
              .to eq(2)

            logger.commit
          end
        end
      end
    end

    it 'raises an error when block is not provided' do
      expect { logger.instrument(mergeability_name: :expensive_operation) }
        .to raise_error(ArgumentError, 'block not given')
    end
  end
end


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

module MergeRequests
  module Mergeability
    class CheckCiStatusService < CheckBaseService
      identifier :ci_must_pass
      description 'Checks whether CI has passed'

      def execute
        return inactive unless merge_request.only_allow_merge_if_pipeline_succeeds?

        if merge_request.mergeable_ci_state?
          success
        else
          failure
        end
      end

      def skip?
        params[:skip_ci_check].present?
      end

      def cacheable?
        false
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::CheckCiStatusService, feature_category: :code_review_workflow do
  subject(:check_ci_status) { described_class.new(merge_request: merge_request, params: params) }

  let_it_be(:project) { build(:project) }
  let_it_be(:merge_request) { build(:merge_request, source_project: project) }
  let(:params) { { skip_ci_check: skip_check } }
  let(:skip_check) { false }

  it_behaves_like 'mergeability check service', :ci_must_pass, 'Checks whether CI has passed'

  describe '#execute' do
    let(:result) { check_ci_status.execute }

    before do
      allow(merge_request)
        .to receive(:only_allow_merge_if_pipeline_succeeds?)
        .and_return(only_allow_merge_if_pipeline_succeeds?)
    end

    context 'when only_allow_merge_if_pipeline_succeeds is true' do
      let(:only_allow_merge_if_pipeline_succeeds?) { true }

      before do
        expect(merge_request).to receive(:mergeable_ci_state?).and_return(mergeable)
      end

      context 'when the merge request is in a mergeable state' do
        let(:mergeable) { true }

        it 'returns a check result with status success' do
          expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
        end
      end

      context 'when the merge request is not in a mergeable state' do
        let(:mergeable) { false }

        it 'returns a check result with status failed' do
          expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
          expect(result.payload[:identifier]).to eq :ci_must_pass
        end
      end
    end

    context 'when only_allow_merge_if_pipeline_succeeds is false' do
      let(:only_allow_merge_if_pipeline_succeeds?) { false }

      it 'returns a check result with inactive status' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::INACTIVE_STATUS
      end
    end
  end

  describe '#skip?' do
    context 'when skip check is true' do
      let(:skip_check) { true }

      it 'returns true' do
        expect(check_ci_status.skip?).to eq true
      end
    end

    context 'when skip check is false' do
      let(:skip_check) { false }

      it 'returns false' do
        expect(check_ci_status.skip?).to eq false
      end
    end
  end

  describe '#cacheable?' do
    it 'returns false' do
      expect(check_ci_status.cacheable?).to eq false
    end
  end
end


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

module MergeRequests
  module Mergeability
    class CheckOpenStatusService < CheckBaseService
      identifier :not_open
      description 'Checks whether the merge request is open'

      def execute
        if merge_request.open?
          success
        else
          failure
        end
      end

      def skip?
        false
      end

      def cacheable?
        false
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::CheckOpenStatusService, feature_category: :code_review_workflow do
  subject(:check_open_status) { described_class.new(merge_request: merge_request, params: {}) }

  let(:merge_request) { build(:merge_request) }

  it_behaves_like 'mergeability check service', :not_open, 'Checks whether the merge request is open'

  describe '#execute' do
    let(:result) { check_open_status.execute }

    before do
      expect(merge_request).to receive(:open?).and_return(open)
    end

    context 'when the merge request is open' do
      let(:open) { true }

      it 'returns a check result with status success' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
      end
    end

    context 'when the merge request is not open' do
      let(:open) { false }

      it 'returns a check result with status failed' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
        expect(result.payload[:identifier]).to eq(:not_open)
      end
    end
  end

  describe '#skip?' do
    it 'returns false' do
      expect(check_open_status.skip?).to eq false
    end
  end

  describe '#cacheable?' do
    it 'returns false' do
      expect(check_open_status.cacheable?).to eq false
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true
module MergeRequests
  module Mergeability
    class CheckBrokenStatusService < CheckBaseService
      identifier :broken_status
      description 'Checks whether the merge request is broken'

      def execute
        if merge_request.broken?
          failure
        else
          success
        end
      end

      def skip?
        false
      end

      def cacheable?
        false
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::CheckBrokenStatusService, feature_category: :code_review_workflow do
  subject(:check_broken_status) { described_class.new(merge_request: merge_request, params: {}) }

  let(:merge_request) { build(:merge_request) }

  it_behaves_like 'mergeability check service', :broken_status, 'Checks whether the merge request is broken'

  describe '#execute' do
    let(:result) { check_broken_status.execute }

    before do
      expect(merge_request).to receive(:broken?).and_return(broken)
    end

    context 'when the merge request is broken' do
      let(:broken) { true }

      it 'returns a check result with status failed' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
        expect(result.payload[:identifier]).to eq(:broken_status)
      end
    end

    context 'when the merge request is not broken' do
      let(:broken) { false }

      it 'returns a check result with status success' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
      end
    end
  end

  describe '#skip?' do
    it 'returns false' do
      expect(check_broken_status.skip?).to eq false
    end
  end

  describe '#cacheable?' do
    it 'returns false' do
      expect(check_broken_status.cacheable?).to eq false
    end
  end
end


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

module MergeRequests
  module Mergeability
    class CheckConflictStatusService < CheckBaseService
      identifier :conflict
      description 'Checks whether the merge request has a conflict'

      def execute
        if merge_request.can_be_merged?
          success
        else
          failure
        end
      end

      def skip?
        false
      end

      def cacheable?
        false
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::CheckConflictStatusService, feature_category: :code_review_workflow do
  subject(:check_conflict_status) { described_class.new(merge_request: merge_request, params: {}) }

  let(:merge_request) { build(:merge_request) }

  it_behaves_like 'mergeability check service', :conflict, 'Checks whether the merge request has a conflict'

  describe '#execute' do
    let(:result) { check_conflict_status.execute }

    before do
      allow(merge_request).to receive(:can_be_merged?).and_return(can_be_merged)
    end

    context 'when MergeRequest#can_be_merged is true' do
      let(:can_be_merged) { true }

      it 'returns a check result with status success' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
      end
    end

    context 'when MergeRequest#can_be_merged is false' do
      let(:can_be_merged) { false }

      it 'returns a check result with status failed' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
        expect(result.payload[:identifier]).to eq(:conflict)
      end
    end
  end

  describe '#skip?' do
    it 'returns false' do
      expect(check_conflict_status.skip?).to eq false
    end
  end

  describe '#cacheable?' do
    it 'returns false' do
      expect(check_conflict_status.cacheable?).to eq false
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true
module MergeRequests
  module Mergeability
    class CheckDiscussionsStatusService < CheckBaseService
      identifier :discussions_not_resolved
      description 'Checks whether the merge request has open discussions'

      def execute
        return inactive unless merge_request.only_allow_merge_if_all_discussions_are_resolved?

        if merge_request.mergeable_discussions_state?
          success
        else
          failure
        end
      end

      def skip?
        params[:skip_discussions_check].present?
      end

      def cacheable?
        false
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::CheckDiscussionsStatusService, feature_category: :code_review_workflow do
  subject(:check_discussions_status) { described_class.new(merge_request: merge_request, params: params) }

  let_it_be(:project) { build(:project) }
  let_it_be(:merge_request) { build(:merge_request, source_project: project) }
  let(:params) { { skip_discussions_check: skip_check } }
  let(:skip_check) { false }

  it_behaves_like 'mergeability check service', :discussions_not_resolved,
    'Checks whether the merge request has open discussions'

  describe '#execute' do
    let(:result) { check_discussions_status.execute }

    before do
      allow(merge_request)
        .to receive(:only_allow_merge_if_all_discussions_are_resolved?)
        .and_return(only_allow_merge_if_all_discussions_are_resolved?)
    end

    context 'when only_allow_merge_if_all_discussions_are_resolved is true' do
      let(:only_allow_merge_if_all_discussions_are_resolved?) { true }

      before do
        allow(merge_request).to receive(:mergeable_discussions_state?).and_return(mergeable)
      end

      context 'when the merge request is in a mergable state' do
        let(:mergeable) { true }

        it 'returns a check result with status success' do
          expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
        end
      end

      context 'when the merge request is not in a mergeable state' do
        let(:mergeable) { false }

        it 'returns a check result with status failed' do
          expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
          expect(result.payload[:identifier]).to eq(:discussions_not_resolved)
        end
      end
    end

    context 'when only_allow_merge_if_all_discussions_are_resolved is false' do
      let(:only_allow_merge_if_all_discussions_are_resolved?) { false }

      it 'returns a check result with inactive status' do
        expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::INACTIVE_STATUS
      end
    end
  end

  describe '#skip?' do
    context 'when skip check is true' do
      let(:skip_check) { true }

      it 'returns true' do
        expect(check_discussions_status.skip?).to eq true
      end
    end

    context 'when skip check is false' do
      let(:skip_check) { false }

      it 'returns false' do
        expect(check_discussions_status.skip?).to eq false
      end
    end
  end

  describe '#cacheable?' do
    it 'returns false' do
      expect(check_discussions_status.cacheable?).to eq false
    end
  end
end


Instruction:
Write RSpec test file for following ruby class
```ruby
# frozen_string_literal: true
module MergeRequests
  module Mergeability
    class CheckBaseService
      attr_reader :merge_request, :params

      class_attribute :identifier, :description

      def self.identifier(new_identifier)
        self.identifier = new_identifier
      end

      def self.description(new_description)
        self.description = new_description
      end

      def initialize(merge_request:, params:)
        @merge_request = merge_request
        @params = params
      end

      def skip?
        raise NotImplementedError
      end

      # When this method is true, we need to implement a cache_key
      def cacheable?
        raise NotImplementedError
      end

      def cache_key
        raise NotImplementedError
      end

      private

      def success(**args)
        Gitlab::MergeRequests::Mergeability::CheckResult
          .success(payload: default_payload(args))
      end

      def failure(**args)
        Gitlab::MergeRequests::Mergeability::CheckResult
          .failed(payload: default_payload(args))
      end

      def inactive(**args)
        Gitlab::MergeRequests::Mergeability::CheckResult
          .inactive(payload: default_payload(args))
      end

      def default_payload(args)
        args.merge(identifier: self.class.identifier)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::Mergeability::CheckBaseService, feature_category: :code_review_workflow do
  subject(:check_base_service) { described_class.new(merge_request: merge_request, params: params) }

  let(:merge_request) { double }
  let(:params) { double }

  describe '.identifier' do
    it 'sets the identifier' do
      described_class.identifier("test")

      expect(described_class.identifier).to eq("test")
    end
  end

  describe '.description' do
    it 'sets the description' do
      described_class.description("test")

      expect(described_class.description).to eq("test")
    end
  end

  describe '#merge_request' do
    it 'returns the merge_request' do
      expect(check_base_service.merge_request).to eq merge_request
    end
  end

  describe '#params' do
    it 'returns the params' do
      expect(check_base_service.params).to eq params
    end
  end

  describe '#skip?' do
    it 'raises NotImplementedError' do
      expect { check_base_service.skip? }.to raise_error(NotImplementedError)
    end
  end

  describe '#cacheable?' do
    it 'raises NotImplementedError' do
      expect { check_base_service.skip? }.to raise_error(NotImplementedError)
    end
  end

  describe '#cache_key?' do
    it 'raises NotImplementedError' do
      expect { check_base_service.skip? }.to raise_error(NotImplementedError)
    end
  end
end


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

module MergeRequests
  module MergeStrategies
    # FromSourceBranch performs a git merge from a merge request's source branch
    # to the target branch, including a squash if needed.
    class FromSourceBranch
      include Gitlab::Utils::StrongMemoize

      delegate :repository, to: :project

      def initialize(merge_request, current_user, merge_params: {}, options: {})
        @merge_request = merge_request
        @current_user = current_user
        @project = merge_request.project
        @merge_params = merge_params
        @options = options
      end

      def validate!
        error_message =
          if source_sha.blank?
            'No source for merge'
          elsif merge_request.should_be_rebased?
            'Only fast-forward merge is allowed for your project. Please update your source branch'
          elsif !merge_request.mergeable?(
            skip_discussions_check: @options[:skip_discussions_check],
            check_mergeability_retry_lease: @options[:check_mergeability_retry_lease]
          )
            'Merge request is not mergeable'
          elsif merge_request.missing_required_squash?
            'This project requires squashing commits when merge requests are accepted.'
          end

        raise_error(error_message) if error_message
      end

      def execute_git_merge!
        result =
          if project.merge_requests_ff_only_enabled
            fast_forward!
          else
            merge_commit!
          end

        result[:squash_commit_sha] = source_sha if merge_request.squash_on_merge?

        result
      end

      private

      attr_reader :merge_request, :current_user, :merge_params, :options, :project

      def source_sha
        if merge_request.squash_on_merge?
          squash_sha!
        else
          merge_request.diff_head_sha
        end
      end
      strong_memoize_attr :source_sha

      def squash_sha!
        squash_result = ::MergeRequests::SquashService
          .new(
            merge_request: merge_request,
            current_user: current_user,
            commit_message: merge_params[:squash_commit_message]
          ).execute

        case squash_result[:status]
        when :success
          squash_result[:squash_sha]
        when :error
          raise_error(squash_result[:message])
        end
      end

      def fast_forward!
        commit_sha = repository.ff_merge(
          current_user,
          source_sha,
          merge_request.target_branch,
          merge_request: merge_request
        )

        { commit_sha: commit_sha }
      end

      def merge_commit!
        commit_sha = repository.merge(
          current_user,
          source_sha,
          merge_request,
          merge_commit_message
        )

        { commit_sha: commit_sha, merge_commit_sha: commit_sha }
      end

      def merge_commit_message
        merge_params[:commit_message] ||
          merge_request.default_merge_commit_message(user: current_user)
      end

      def raise_error(message)
        raise ::MergeRequests::MergeStrategies::StrategyError, message
      end
    end
  end
end

::MergeRequests::MergeStrategies::FromSourceBranch.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::MergeStrategies::FromSourceBranch, feature_category: :code_review_workflow do
  let_it_be(:user) { create(:user) }
  let_it_be(:user2) { create(:user) }

  let(:merge_request) { create(:merge_request, :simple, author: user2, assignees: [user2]) }
  let(:project) { merge_request.project }

  subject(:strategy) { described_class.new(merge_request, user) }

  before do
    project.add_maintainer(user)
  end

  describe '#validate!' do
    context 'when source is missing' do
      before do
        allow(merge_request).to receive(:diff_head_sha).and_return(nil)
      end

      it 'raises source error when source is missing' do
        error_message = 'No source for merge'

        expect { strategy.validate! }
          .to raise_exception(MergeRequests::MergeStrategies::StrategyError, error_message)
      end
    end

    context 'when merge request should be rebased' do
      before do
        allow(merge_request).to receive(:should_be_rebased?).and_return(true)
      end

      it 'raises needs rebase error' do
        error_message = 'Only fast-forward merge is allowed for your project. Please update your source branch'

        expect { strategy.validate! }
          .to raise_exception(MergeRequests::MergeStrategies::StrategyError, error_message)
      end
    end

    context 'when merge request should be squashed but is not' do
      before do
        merge_request.target_project.project_setting.squash_always!
        merge_request.update!(squash: false)
      end

      it 'raises squashing error' do
        error_message = 'This project requires squashing commits when merge requests are accepted.'

        expect { strategy.validate! }
          .to raise_exception(MergeRequests::MergeStrategies::StrategyError, error_message)
      end
    end
  end

  describe '#execute_git_merge!' do
    context 'when fast-forward is required' do
      before do
        project.merge_method = :ff
        project.save!
      end

      it 'performs a fast-forward merge' do
        expect(merge_request.target_project.repository).to receive(:ff_merge).and_return('1234')

        strategy.execute_git_merge!
      end
    end

    context 'when a merge commit is required' do
      before do
        project.merge_method = :merge
        project.save!
      end

      it 'performs standard merge' do
        expect(merge_request.target_project.repository).to receive(:merge).and_return('1234')

        strategy.execute_git_merge!
      end
    end
  end
end