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

module MergeRequests
  class RefreshService < MergeRequests::BaseService
    include Gitlab::Utils::StrongMemoize
    attr_reader :push

    def execute(oldrev, newrev, ref)
      @push = Gitlab::Git::Push.new(@project, oldrev, newrev, ref)
      return true unless @push.branch_push?

      refresh_merge_requests!
    end

    private

    def refresh_merge_requests!
      # n + 1: https://gitlab.com/gitlab-org/gitlab-foss/issues/60289
      Gitlab::GitalyClient.allow_n_plus_1_calls { find_new_commits }

      # Be sure to close outstanding MRs before reloading them to avoid generating an
      # empty diff during a manual merge
      close_upon_missing_source_branch_ref
      post_merge_manually_merged
      link_forks_lfs_objects
      reload_merge_requests

      merge_requests_for_source_branch.each do |mr|
        outdate_suggestions(mr)
        abort_auto_merges(mr)
        mark_pending_todos_done(mr)
      end

      abort_ff_merge_requests_with_when_pipeline_succeeds
      cache_merge_requests_closing_issues

      merge_requests_for_source_branch.each do |mr|
        # Leave a system note if a branch was deleted/added
        if branch_added_or_removed?
          comment_mr_branch_presence_changed(mr)
        end

        notify_about_push(mr)
        mark_mr_as_draft_from_commits(mr)
        execute_mr_web_hooks(mr)
        # Run at the end of the loop to avoid any potential contention on the MR object
        refresh_pipelines_on_merge_requests(mr)
        merge_request_activity_counter.track_mr_including_ci_config(user: mr.author, merge_request: mr)
      end

      true
    end

    def branch_added_or_removed?
      strong_memoize(:branch_added_or_removed) do
        @push.branch_added? || @push.branch_removed?
      end
    end

    def close_upon_missing_source_branch_ref
      # MergeRequest#reload_diff ignores not opened MRs. This means it won't
      # create an `empty` diff for `closed` MRs without a source branch, keeping
      # the latest diff state as the last _valid_ one.
      merge_requests_for_source_branch.reject(&:source_branch_exists?).each do |mr|
        MergeRequests::CloseService
          .new(project: mr.target_project, current_user: @current_user)
          .execute(mr)
      end
    end

    # Collect open merge requests that target same branch we push into
    # and close if push to master include last commit from merge request
    # We need this to close(as merged) merge requests that were merged into
    # target branch manually
    # rubocop: disable CodeReuse/ActiveRecord
    def post_merge_manually_merged
      commit_ids = @commits.map(&:id)
      merge_requests = @project.merge_requests.opened
        .preload_project_and_latest_diff
        .preload_latest_diff_commit
        .where(target_branch: @push.branch_name).to_a
        .select(&:diff_head_commit)
        .select do |merge_request|
          commit_ids.include?(merge_request.diff_head_sha) &&
            merge_request.merge_request_diff.state != 'empty'
        end
      merge_requests = filter_merge_requests(merge_requests)

      return if merge_requests.empty?

      analyzer = Gitlab::BranchPushMergeCommitAnalyzer.new(
        @commits.reverse,
        relevant_commit_ids: merge_requests.map(&:diff_head_sha)
      )

      merge_requests.each do |merge_request|
        sha = analyzer.get_merge_commit(merge_request.diff_head_sha)
        merge_request.merge_commit_sha = sha
        merge_request.merged_commit_sha = sha

        MergeRequests::PostMergeService
          .new(project: merge_request.target_project, current_user: @current_user)
          .execute(merge_request)
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    # Link LFS objects that exists in forks but does not exists in merge requests
    # target project
    def link_forks_lfs_objects
      return unless @push.branch_updated?

      merge_requests_for_forks.find_each do |mr|
        LinkLfsObjectsService
          .new(project: mr.target_project)
          .execute(mr, oldrev: @push.oldrev, newrev: @push.newrev)
      end
    end

    # Refresh merge request diff if we push to source or target branch of merge request
    # Note: we should update merge requests from forks too
    def reload_merge_requests
      merge_requests = @project.merge_requests.opened
        .by_source_or_target_branch(@push.branch_name)
        .preload_project_and_latest_diff

      merge_requests_from_forks = merge_requests_for_forks
        .preload_project_and_latest_diff

      merge_requests_array = merge_requests.to_a + merge_requests_from_forks.to_a
      filter_merge_requests(merge_requests_array).each do |merge_request|
        skip_merge_status_trigger = true

        if branch_and_project_match?(merge_request) || @push.force_push?
          merge_request.reload_diff(current_user)
          # Clear existing merge error if the push were directed at the
          # source branch. Clearing the error when the target branch
          # changes will hide the error from the user.
          merge_request.merge_error = nil

          # Don't skip trigger since we to update the MR's merge status in real-time
          # when the push if for the MR's source branch and project.
          skip_merge_status_trigger = false
        elsif merge_request.merge_request_diff.includes_any_commits?(push_commit_ids)
          merge_request.reload_diff(current_user)
        end

        merge_request.skip_merge_status_trigger = skip_merge_status_trigger
        merge_request.mark_as_unchecked
      end

      # Upcoming method calls need the refreshed version of
      # @source_merge_requests diffs (for MergeRequest#commit_shas for instance).
      merge_requests_for_source_branch(reload: true)
    end

    def push_commit_ids
      @push_commit_ids ||= @commits.map(&:id)
    end

    def branch_and_project_match?(merge_request)
      merge_request.source_project == @project &&
        merge_request.source_branch == @push.branch_name
    end

    def outdate_suggestions(merge_request)
      outdate_service.execute(merge_request)
    end

    def outdate_service
      @outdate_service ||= Suggestions::OutdateService.new
    end

    def abort_auto_merges?(merge_request)
      merge_request.merge_params.with_indifferent_access[:sha] != @push.newrev
    end

    def abort_auto_merges(merge_request)
      return unless abort_auto_merges?(merge_request)

      abort_auto_merge(merge_request, 'source branch was updated')
    end

    def abort_ff_merge_requests_with_when_pipeline_succeeds
      return unless @project.ff_merge_must_be_possible?

      merge_requests_with_auto_merge_enabled_to(@push.branch_name).each do |merge_request|
        next unless merge_request.auto_merge_strategy == AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
        next unless merge_request.should_be_rebased?

        abort_auto_merge_with_todo(merge_request, 'target branch was updated')
      end
    end

    def abort_auto_merge_with_todo(merge_request, reason)
      response = abort_auto_merge(merge_request, reason)
      response = ServiceResponse.new(**response)
      return unless response.success?

      todo_service.merge_request_became_unmergeable(merge_request)
    end

    def merge_requests_with_auto_merge_enabled_to(target_branch)
      @project
        .merge_requests
        .by_target_branch(target_branch)
        .with_auto_merge_enabled
    end

    def mark_pending_todos_done(merge_request)
      todo_service.merge_request_push(merge_request, @current_user)
    end

    def find_new_commits
      if @push.branch_added?
        @commits = []

        merge_request = merge_requests_for_source_branch.first
        return unless merge_request

        begin
          # Since any number of commits could have been made to the restored branch,
          # find the common root to see what has been added.
          common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @push.newrev)
          # If the a commit no longer exists in this repo, gitlab_git throws
          # a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52
          @commits = @project.repository.commits_between(common_ref, @push.newrev) if common_ref
        rescue StandardError
        end
      elsif @push.branch_removed?
        # No commits for a deleted branch.
        @commits = []
      else
        @commits = @project.repository.commits_between(@push.oldrev, @push.newrev)
      end
    end

    # Add comment about branches being deleted or added to merge requests
    def comment_mr_branch_presence_changed(merge_request)
      presence = @push.branch_added? ? :add : :delete

      SystemNoteService.change_branch_presence(
        merge_request, merge_request.project, @current_user,
        :source, @push.branch_name, presence)
    end

    # Add comment about pushing new commits to merge requests and send notification emails
    #
    def notify_about_push(merge_request)
      return unless @commits.present?

      mr_commit_ids = Set.new(merge_request.commit_shas)

      new_commits, existing_commits = @commits.partition do |commit|
        mr_commit_ids.include?(commit.id)
      end

      SystemNoteService.add_commits(
        merge_request, merge_request.project,
        @current_user, new_commits,
        existing_commits, @push.oldrev
      )

      notification_service.push_to_merge_request(merge_request, @current_user, new_commits: new_commits, existing_commits: existing_commits)
    end

    def mark_mr_as_draft_from_commits(merge_request)
      return unless @commits.present?

      commit_shas = merge_request.commit_shas

      draft_commit = @commits.detect do |commit|
        commit.draft? && commit_shas.include?(commit.sha)
      end

      if draft_commit && !merge_request.draft?
        merge_request.update(title: merge_request.draft_title)
        SystemNoteService.add_merge_request_draft_from_commit(
          merge_request,
          merge_request.project,
          @current_user,
          draft_commit
        )
      end
    end

    # Call merge request webhook with update branches
    def execute_mr_web_hooks(merge_request)
      execute_hooks(merge_request, 'update', old_rev: @push.oldrev)
    end

    # If the merge requests closes any issues, save this information in the
    # `MergeRequestsClosingIssues` model (as a performance optimization).
    # rubocop: disable CodeReuse/ActiveRecord
    def cache_merge_requests_closing_issues
      @project.merge_requests.where(source_branch: @push.branch_name).each do |merge_request|
        merge_request.cache_merge_request_closes_issues!(@current_user)
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def filter_merge_requests(merge_requests)
      merge_requests.uniq.select(&:source_project)
    end

    def merge_requests_for_source_branch(reload: false)
      @source_merge_requests = nil if reload
      @source_merge_requests ||= merge_requests_for(@push.branch_name)
    end

    def merge_requests_for_forks
      @merge_requests_for_forks ||=
        MergeRequest
          .opened
          .from_project(project)
          .from_source_branches(@push.branch_name)
          .from_fork
    end
  end
end

MergeRequests::RefreshService.prepend_mod_with('MergeRequests::RefreshService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_workflow do
  include ProjectForksHelper
  include UserHelpers

  let(:project) { create(:project, :repository) }
  let(:user) { create(:user) }
  let(:service) { described_class }

  describe '#execute' do
    before do
      @user = create(:user)
      group = create(:group)
      group.add_owner(@user)

      @project = create(:project, :repository, namespace: group)
      @fork_project = fork_project(@project, @user, repository: true)

      @merge_request = create(
        :merge_request,
        source_project: @project,
        source_branch: 'master',
        target_branch: 'feature',
        target_project: @project,
        auto_merge_enabled: true,
        auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
        merge_user: @user
      )

      @another_merge_request = create(
        :merge_request,
        source_project: @project,
        source_branch: 'master',
        target_branch: 'test',
        target_project: @project,
        auto_merge_enabled: true,
        auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
        merge_user: @user
      )

      @fork_merge_request = create(
        :merge_request,
        source_project: @fork_project,
        source_branch: 'master',
        target_branch: 'feature',
        target_project: @project
      )

      @build_failed_todo = create(
        :todo,
        :build_failed,
        user: @user,
        project: @project,
        target: @merge_request,
        author: @user
      )

      @fork_build_failed_todo = create(
        :todo,
        :build_failed,
        user: @user,
        project: @project,
        target: @merge_request,
        author: @user
      )

      @commits = @merge_request.commits

      @oldrev = @commits.last.id
      @newrev = @commits.first.id
    end

    context 'push to origin repo source branch' do
      let(:refresh_service) { service.new(project: @project, current_user: @user) }
      let(:notification_service) { spy('notification_service') }

      before do
        allow(refresh_service).to receive(:execute_hooks)
        allow(NotificationService).to receive(:new) { notification_service }
      end

      context 'query count' do
        it 'does not execute a lot of queries' do
          # Hardcoded the query limit since the queries can also be reduced even
          # if there are the same number of merge requests (e.g. by preloading
          # associations). This should also fail in case additional queries are
          # added elsewhere that affected this service.
          #
          # The limit is based on the number of queries executed at the current
          # state of the service. As we reduce the number of queries executed in
          # this service, the limit should be reduced as well.
          expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }
            .not_to exceed_query_limit(260)
        end
      end

      it 'executes hooks with update action' do
        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
        reload_mrs

        expect(refresh_service).to have_received(:execute_hooks)
          .with(@merge_request, 'update', old_rev: @oldrev)

        expect(notification_service).to have_received(:push_to_merge_request)
          .with(@merge_request, @user, new_commits: anything, existing_commits: anything)
        expect(notification_service).to have_received(:push_to_merge_request)
          .with(@another_merge_request, @user, new_commits: anything, existing_commits: anything)

        expect(@merge_request.notes).not_to be_empty
        expect(@merge_request).to be_open
        expect(@merge_request.auto_merge_enabled).to be_falsey
        expect(@merge_request.diff_head_sha).to eq(@newrev)
        expect(@fork_merge_request).to be_open
        expect(@fork_merge_request.notes).to be_empty
        expect(@build_failed_todo).to be_done
        expect(@fork_build_failed_todo).to be_done
      end

      it 'triggers mergeRequestMergeStatusUpdated GraphQL subscription conditionally' do
        expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@merge_request)
        expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@another_merge_request)
        expect(GraphqlTriggers).not_to receive(:merge_request_merge_status_updated).with(@fork_merge_request)

        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
      end

      context 'when a merge error exists' do
        let(:error_message) { 'This is a merge error' }

        before do
          @merge_request = create(:merge_request,
            source_project: @project,
            source_branch: 'feature',
            target_branch: 'master',
            target_project: @project,
            merge_error: error_message)
        end

        it 'clears merge errors when pushing to the source branch' do
          expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/feature') }
            .to change { @merge_request.reload.merge_error }
            .from(error_message)
            .to(nil)
        end

        it 'does not clear merge errors when pushing to the target branch' do
          expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }
            .not_to change { @merge_request.reload.merge_error }
        end
      end

      it 'reloads source branch MRs memoization' do
        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')

        expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }.to change {
          refresh_service.instance_variable_get(:@source_merge_requests).first.merge_request_diff
        }
      end

      it 'outdates MR suggestions' do
        expect_next_instance_of(Suggestions::OutdateService) do |service|
          expect(service).to receive(:execute).with(@merge_request).and_call_original
          expect(service).to receive(:execute).with(@another_merge_request).and_call_original
        end

        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
      end

      context 'when source branch ref does not exists' do
        before do
          ::Branches::DeleteService.new(@project, @user).execute(@merge_request.source_branch)
        end

        it 'closes MRs without source branch ref' do
          expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }
            .to change { @merge_request.reload.state }
            .from('opened')
            .to('closed')

          expect(@fork_merge_request.reload).to be_open
        end

        it 'does not change the merge request diff' do
          expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }
            .not_to change { @merge_request.reload.merge_request_diff }
        end
      end

      it 'calls the merge request activity counter' do
        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .to receive(:track_mr_including_ci_config)
          .with(user: @merge_request.author, merge_request: @merge_request)

        expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
          .to receive(:track_mr_including_ci_config)
          .with(user: @another_merge_request.author, merge_request: @another_merge_request)

        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
      end
    end

    context 'when pipeline exists for the source branch' do
      let!(:pipeline) { create(:ci_empty_pipeline, ref: @merge_request.source_branch, project: @project, sha: @commits.first.sha) }

      subject { service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/master') }

      it 'updates the head_pipeline_id for @merge_request', :sidekiq_inline do
        expect { subject }.to change { @merge_request.reload.head_pipeline_id }.from(nil).to(pipeline.id)
      end

      it 'does not update the head_pipeline_id for @fork_merge_request' do
        expect { subject }.not_to change { @fork_merge_request.reload.head_pipeline_id }
      end
    end

    context 'Pipelines for merge requests', :sidekiq_inline do
      before do
        stub_ci_pipeline_yaml_file(config)
      end

      subject { service.new(project: project, current_user: @user).execute(@oldrev, @newrev, ref) }

      let(:ref) { 'refs/heads/master' }
      let(:project) { @project }

      context "when .gitlab-ci.yml has merge_requests keywords" do
        let(:config) do
          YAML.dump({
            test: {
              stage: 'test',
              script: 'echo',
              only: ['merge_requests']
            }
          })
        end

        it 'create detached merge request pipeline with commits' do
          expect { subject }
            .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
            .and change { @another_merge_request.pipelines_for_merge_request.count }.by(0)

          expect(@merge_request.has_commits?).to be_truthy
          expect(@another_merge_request.has_commits?).to be_falsy
        end

        context 'when "push_options: nil" is passed' do
          let(:service_instance) { service.new(project: project, current_user: @user, params: { push_options: nil }) }

          subject { service_instance.execute(@oldrev, @newrev, ref) }

          it 'creates a detached merge request pipeline with commits' do
            expect { subject }
              .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
              .and change { @another_merge_request.pipelines_for_merge_request.count }.by(0)

            expect(@merge_request.has_commits?).to be_truthy
            expect(@another_merge_request.has_commits?).to be_falsy
          end
        end

        context 'when ci.skip push_options are passed' do
          let(:params) { { push_options: { ci: { skip: true } } } }
          let(:service_instance) { service.new(project: project, current_user: @user, params: params) }

          subject { service_instance.execute(@oldrev, @newrev, ref) }

          it 'creates a skipped detached merge request pipeline with commits' do
            expect { subject }
              .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
              .and change { @another_merge_request.pipelines_for_merge_request.count }.by(0)

            expect(@merge_request.has_commits?).to be_truthy
            expect(@another_merge_request.has_commits?).to be_falsy

            pipeline = @merge_request.pipelines_for_merge_request.last
            expect(pipeline).to be_skipped
          end
        end

        it 'does not create detached merge request pipeline for forked project' do
          expect { subject }
            .not_to change { @fork_merge_request.pipelines_for_merge_request.count }
        end

        it 'create detached merge request pipeline for non-fork merge request' do
          subject

          expect(@merge_request.pipelines_for_merge_request.first)
            .to be_detached_merge_request_pipeline
        end

        context 'when service is hooked by target branch' do
          let(:ref) { 'refs/heads/feature' }

          it 'does not create detached merge request pipeline' do
            expect { subject }
              .not_to change { @merge_request.pipelines_for_merge_request.count }
          end
        end

        context 'when service runs on forked project' do
          let(:project) { @fork_project }

          it 'creates detached merge request pipeline for fork merge request' do
            expect { subject }
              .to change { @fork_merge_request.pipelines_for_merge_request.count }.by(1)

            merge_request_pipeline = @fork_merge_request.pipelines_for_merge_request.first
            expect(merge_request_pipeline).to be_detached_merge_request_pipeline
            expect(merge_request_pipeline.project).to eq(@project)
          end
        end

        context "when branch pipeline was created before a detaced merge request pipeline has been created" do
          before do
            create(
              :ci_pipeline,
              project: @merge_request.source_project,
              sha: @merge_request.diff_head_sha,
              ref: @merge_request.source_branch,
              tag: false
            )

            subject
          end

          it 'sets the latest detached merge request pipeline as a head pipeline' do
            @merge_request.reload
            expect(@merge_request.actual_head_pipeline).to be_merge_request_event
          end

          it 'returns pipelines in correct order' do
            @merge_request.reload
            expect(@merge_request.all_pipelines.first).to be_merge_request_event
            expect(@merge_request.all_pipelines.second).to be_push
          end
        end

        context "when MergeRequestUpdateWorker is retried by an exception" do
          it 'does not re-create a duplicate detached merge request pipeline' do
            expect do
              service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/master')
            end.to change { @merge_request.pipelines_for_merge_request.count }.by(1)

            expect do
              service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/master')
            end.not_to change { @merge_request.pipelines_for_merge_request.count }
          end
        end

        context 'when the pipeline should be skipped' do
          it 'saves a skipped detached merge request pipeline' do
            project.repository.create_file(
              @user, 'new-file.txt', 'A new file',
              message: '[skip ci] This is a test',
              branch_name: 'master'
            )

            expect { subject }
              .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
            expect(@merge_request.pipelines_for_merge_request.last).to be_skipped
          end
        end
      end

      context "when .gitlab-ci.yml does not have merge_requests keywords" do
        let(:config) do
          YAML.dump({
            test: {
              stage: 'test',
              script: 'echo'
            }
          })
        end

        it 'does not create a detached merge request pipeline' do
          expect { subject }
            .not_to change { @merge_request.pipelines_for_merge_request.count }
        end
      end

      context 'when .gitlab-ci.yml is invalid' do
        let(:config) { 'invalid yaml file' }

        it 'persists a pipeline with config error' do
          expect { subject }
            .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
          expect(@merge_request.pipelines_for_merge_request.last).to be_failed
          expect(@merge_request.pipelines_for_merge_request.last).to be_config_error
        end
      end

      context 'when .gitlab-ci.yml file is valid but has a logical error' do
        let(:config) do
          YAML.dump({
            build: {
              script: 'echo "Valid yaml syntax, but..."',
              only: ['master']
            },
            test: {
              script: 'echo "... I depend on build, which does not run."',
              only: ['merge_request'],
              needs: ['build']
            }
          })
        end

        it 'persists a pipeline with config error' do
          expect { subject }
            .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
          expect(@merge_request.pipelines_for_merge_request.last).to be_failed
          expect(@merge_request.pipelines_for_merge_request.last).to be_config_error
        end
      end
    end

    context 'push to origin repo source branch' do
      let(:refresh_service) { service.new(project: @project, current_user: @user) }
      let(:notification_service) { spy('notification_service') }

      before do
        allow(refresh_service).to receive(:execute_hooks)
        allow(NotificationService).to receive(:new) { notification_service }
        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
        reload_mrs
      end

      it 'executes hooks with update action' do
        expect(refresh_service).to have_received(:execute_hooks)
          .with(@merge_request, 'update', old_rev: @oldrev)
        expect(notification_service).to have_received(:push_to_merge_request)
          .with(@merge_request, @user, new_commits: anything, existing_commits: anything)
        expect(notification_service).to have_received(:push_to_merge_request)
          .with(@another_merge_request, @user, new_commits: anything, existing_commits: anything)

        expect(@merge_request.notes).not_to be_empty
        expect(@merge_request).to be_open
        expect(@merge_request.auto_merge_enabled).to be_falsey
        expect(@merge_request.diff_head_sha).to eq(@newrev)
        expect(@fork_merge_request).to be_open
        expect(@fork_merge_request.notes).to be_empty
        expect(@build_failed_todo).to be_done
        expect(@fork_build_failed_todo).to be_done
      end
    end

    context 'push to origin repo target branch' do
      context 'when all MRs to the target branch had diffs' do
        before do
          service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
          reload_mrs
        end

        it 'updates the merge state' do
          expect(@merge_request).to be_merged
          expect(@fork_merge_request).to be_merged
          expect(@build_failed_todo).to be_done
          expect(@fork_build_failed_todo).to be_done

          expect(@merge_request.resource_state_events.last.state).to eq('merged')
          expect(@fork_merge_request.resource_state_events.last.state).to eq('merged')
        end
      end

      context 'when an MR to be closed was empty already' do
        let!(:empty_fork_merge_request) do
          create(
            :merge_request,
            source_project: @fork_project,
            source_branch: 'master',
            target_branch: 'master',
            target_project: @project
          )
        end

        before do
          # This spec already has a fake push, so pretend that we were targeting
          # feature all along.
          empty_fork_merge_request.update_columns(target_branch: 'feature')

          service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
          reload_mrs
          empty_fork_merge_request.reload
        end

        it 'only updates the non-empty MRs' do
          expect(@merge_request).to be_merged
          expect(@fork_merge_request).to be_merged

          expect(empty_fork_merge_request).to be_open
          expect(empty_fork_merge_request.merge_request_diff.state).to eq('empty')
          expect(empty_fork_merge_request.notes).to be_empty

          expect(@merge_request.resource_state_events.last.state).to eq('merged')
          expect(@fork_merge_request.resource_state_events.last.state).to eq('merged')
        end
      end

      context 'manual merge of source branch' do
        before do
          # Merge master -> feature branch
          @project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, 'Test message')
          commit = @project.repository.commit('feature')
          service.new(project: @project, current_user: @user).execute(@oldrev, commit.id, 'refs/heads/feature')
          reload_mrs
        end

        it 'updates the merge state' do
          expect(@merge_request.resource_state_events.last.state).to eq('merged')
          expect(@fork_merge_request.resource_state_events.last.state).to eq('merged')

          expect(@merge_request).to be_merged
          expect(@merge_request.diffs.size).to be > 0
          expect(@fork_merge_request).to be_merged
          expect(@build_failed_todo).to be_done
          expect(@fork_build_failed_todo).to be_done
        end
      end
    end

    context 'push to fork repo source branch' do
      let(:refresh_service) { service.new(project: @fork_project, current_user: @user) }

      def refresh
        allow(refresh_service).to receive(:execute_hooks)
        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
        reload_mrs
      end

      context 'open fork merge request' do
        it 'calls MergeRequests::LinkLfsObjectsService#execute' do
          expect_next_instance_of(MergeRequests::LinkLfsObjectsService) do |svc|
            expect(svc).to receive(:execute).with(@fork_merge_request, oldrev: @oldrev, newrev: @newrev)
          end

          refresh
        end

        it 'executes hooks with update action' do
          refresh

          expect(refresh_service).to have_received(:execute_hooks)
            .with(@fork_merge_request, 'update', old_rev: @oldrev)

          expect(@merge_request.notes).to be_empty
          expect(@merge_request).to be_open
          expect(@fork_merge_request.notes.last.note).to include('added 28 commits')
          expect(@fork_merge_request).to be_open
          expect(@build_failed_todo).to be_pending
          expect(@fork_build_failed_todo).to be_pending
        end

        it 'outdates opened forked MR suggestions' do
          expect_next_instance_of(Suggestions::OutdateService) do |service|
            expect(service).to receive(:execute).with(@fork_merge_request).and_call_original
          end

          refresh
        end
      end

      context 'closed fork merge request' do
        before do
          @fork_merge_request.close!
        end

        it 'do not execute hooks with update action' do
          refresh

          expect(refresh_service).not_to have_received(:execute_hooks)
        end

        it 'updates merge request to closed state' do
          refresh

          expect(@merge_request.notes).to be_empty
          expect(@merge_request).to be_open
          expect(@fork_merge_request.notes).to be_empty
          expect(@fork_merge_request).to be_closed
          expect(@build_failed_todo).to be_pending
          expect(@fork_build_failed_todo).to be_pending
        end
      end
    end

    context 'push to fork repo target branch' do
      describe 'changes to merge requests' do
        before do
          service.new(project: @fork_project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
          reload_mrs
        end

        it 'updates the merge request state' do
          expect(@merge_request.notes).to be_empty
          expect(@merge_request).to be_open
          expect(@fork_merge_request.notes).to be_empty
          expect(@fork_merge_request).to be_open
          expect(@build_failed_todo).to be_pending
          expect(@fork_build_failed_todo).to be_pending
        end
      end

      describe 'merge request diff' do
        it 'does not reload the diff of the merge request made from fork' do
          expect do
            service.new(project: @fork_project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
          end.not_to change { @fork_merge_request.reload.merge_request_diff }
        end
      end
    end

    context 'forked projects with the same source branch name as target branch' do
      let!(:first_commit) do
        @fork_project.repository.create_file(
          @user,
          'test1.txt',
          'Test data',
          message: 'Test commit',
          branch_name: 'master'
        )
      end

      let!(:second_commit) do
        @fork_project.repository.create_file(
          @user,
          'test2.txt',
          'More test data',
          message: 'Second test commit',
          branch_name: 'master'
        )
      end

      let!(:forked_master_mr) do
        create(
          :merge_request,
          source_project: @fork_project,
          source_branch: 'master',
          target_branch: 'master',
          target_project: @project
        )
      end

      let(:force_push_commit) { @project.commit('feature').id }

      it 'reloads a new diff for a push to the forked project' do
        expect do
          service.new(project: @fork_project, current_user: @user).execute(@oldrev, first_commit, 'refs/heads/master')
          reload_mrs
        end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
      end

      it 'reloads a new diff for a force push to the source branch' do
        expect do
          service.new(project: @fork_project, current_user: @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
          reload_mrs
        end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
      end

      it 'reloads a new diff for a force push to the target branch' do
        expect do
          service.new(project: @project, current_user: @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
          reload_mrs
        end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
      end

      it 'reloads a new diff for a push to the target project that contains a commit in the MR' do
        expect do
          service.new(project: @project, current_user: @user).execute(@oldrev, first_commit, 'refs/heads/master')
          reload_mrs
        end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
      end

      it 'does not increase the diff count for a new push to target branch' do
        new_commit = @project.repository.create_file(
          @user,
          'new-file.txt',
          'A new file',
          message: 'This is a test',
          branch_name: 'master'
        )

        expect do
          service.new(project: @project, current_user: @user).execute(@newrev, new_commit, 'refs/heads/master')
          reload_mrs
        end.not_to change { forked_master_mr.merge_request_diffs.count }
      end
    end

    context 'push to origin repo target branch after fork project was removed' do
      before do
        @fork_project.destroy!
        service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
        reload_mrs
      end

      it 'updates the merge request state' do
        expect(@merge_request.resource_state_events.last.state).to eq('merged')

        expect(@merge_request).to be_merged
        expect(@fork_merge_request).to be_open
        expect(@fork_merge_request.notes).to be_empty
        expect(@build_failed_todo).to be_done
        expect(@fork_build_failed_todo).to be_done
      end
    end

    context 'push new branch that exists in a merge request' do
      let(:refresh_service) { service.new(project: @fork_project, current_user: @user) }

      it 'refreshes the merge request' do
        expect(refresh_service).to receive(:execute_hooks)
                                       .with(@fork_merge_request, 'update', old_rev: Gitlab::Git::BLANK_SHA)
        allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev)

        refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master')
        reload_mrs

        expect(@merge_request.notes).to be_empty
        expect(@merge_request).to be_open

        notes = @fork_merge_request.notes.reorder(:created_at).map(&:note)
        expect(notes[0]).to include('restored source branch `master`')
        expect(notes[1]).to include('added 28 commits')
        expect(@fork_merge_request).to be_open
      end
    end

    context 'merge request metrics' do
      let(:user) { create(:user) }
      let(:project) { create(:project, :repository) }
      let(:issue) { create(:issue, project: project) }
      let(:commit) { project.commit }

      before do
        project.add_developer(user)

        allow(commit).to receive_messages(
          safe_message: "Closes #{issue.to_reference}",
          references: [issue],
          author_name: user.name,
          author_email: user.email,
          committed_date: Time.current
        )
      end

      context 'when the merge request is sourced from the same project' do
        it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do
          allow_any_instance_of(MergeRequest).to receive(:commits).and_return(
            CommitCollection.new(project, [commit], 'close-by-commit')
          )

          merge_request = create(
            :merge_request,
            target_branch: 'master',
            source_branch: 'close-by-commit',
            source_project: project
          )

          refresh_service = service.new(project: project, current_user: user)
          allow(refresh_service).to receive(:execute_hooks)
          refresh_service.execute(@oldrev, @newrev, 'refs/heads/close-by-commit')

          issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
          expect(issue_ids).to eq([issue.id])
        end
      end

      context 'when the merge request is sourced from a different project' do
        it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do
          forked_project = fork_project(project, user, repository: true)

          allow_any_instance_of(MergeRequest).to receive(:commits).and_return(
            CommitCollection.new(forked_project, [commit], 'close-by-commit')
          )

          merge_request = create(
            :merge_request,
            target_branch: 'master',
            target_project: project,
            source_branch: 'close-by-commit',
            source_project: forked_project
          )

          refresh_service = service.new(project: forked_project, current_user: user)
          allow(refresh_service).to receive(:execute_hooks)
          refresh_service.execute(@oldrev, @newrev, 'refs/heads/close-by-commit')

          issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
          expect(issue_ids).to eq([issue.id])
        end
      end
    end

    context 'marking the merge request as draft' do
      let(:refresh_service) { service.new(project: @project, current_user: @user) }

      before do
        allow(refresh_service).to receive(:execute_hooks)
      end

      it 'marks the merge request as draft from fixup commits' do
        fixup_merge_request = create(
          :merge_request,
          source_project: @project,
          source_branch: 'wip',
          target_branch: 'master',
          target_project: @project
        )
        commits = fixup_merge_request.commits
        oldrev = commits.last.id
        newrev = commits.first.id

        refresh_service.execute(oldrev, newrev, 'refs/heads/wip')
        fixup_merge_request.reload

        expect(fixup_merge_request.draft?).to eq(true)
        expect(fixup_merge_request.notes.last.note).to match(
          /marked this merge request as \*\*draft\*\* from #{Commit.reference_pattern}/
        )
      end

      it 'references the commit that caused the draft status' do
        draft_merge_request = create(
          :merge_request,
          source_project: @project,
          source_branch: 'wip',
          target_branch: 'master',
          target_project: @project
        )

        commits = draft_merge_request.commits
        oldrev = commits.last.id
        newrev = commits.first.id
        draft_commit = draft_merge_request.commits.find(&:draft?)

        refresh_service.execute(oldrev, newrev, 'refs/heads/wip')

        expect(draft_merge_request.reload.notes.last.note).to eq(
          "marked this merge request as **draft** from #{draft_commit.id}"
        )
      end

      it 'does not mark as draft based on commits that do not belong to an MR' do
        allow(refresh_service).to receive(:find_new_commits)

        refresh_service.instance_variable_set(:@commits,
          [
            double(
              id: 'aaaaaaa',
              sha: 'aaaaaaa',
              short_id: 'aaaaaaa',
              title: 'Fix issue',
              draft?: false
            ),
            double(
              id: 'bbbbbbb',
              sha: 'bbbbbbbb',
              short_id: 'bbbbbbb',
              title: 'fixup! Fix issue',
              draft?: true,
              to_reference: 'bbbbbbb'
            )
          ])

        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
        reload_mrs

        expect(@merge_request.draft?).to be_falsey
      end
    end

    def reload_mrs
      @merge_request.reload
      @fork_merge_request.reload
      @build_failed_todo.reload
      @fork_build_failed_todo.reload
    end
  end

  describe 'updating merge_commit' do
    let(:service) { described_class.new(project: project, current_user: user) }
    let(:user) { create(:user) }
    let(:project) { create(:project, :repository) }

    let(:oldrev) { TestEnv::BRANCH_SHA['merge-commit-analyze-before'] }
    let(:newrev) { TestEnv::BRANCH_SHA['merge-commit-analyze-after'] } # Pretend branch is now updated

    let!(:merge_request) do
      create(
        :merge_request,
        source_project: project,
        source_branch: 'merge-commit-analyze-after',
        target_branch: 'merge-commit-analyze-before',
        target_project: project,
        merge_user: user
      )
    end

    let!(:merge_request_side_branch) do
      create(
        :merge_request,
        source_project: project,
        source_branch: 'merge-commit-analyze-side-branch',
        target_branch: 'merge-commit-analyze-before',
        target_project: project,
        merge_user: user
      )
    end

    subject { service.execute(oldrev, newrev, 'refs/heads/merge-commit-analyze-before') }

    context 'feature enabled' do
      it "updates merge requests' merge_commit and merged_commit values", :aggregate_failures do
        expect(Gitlab::BranchPushMergeCommitAnalyzer).to receive(:new).and_wrap_original do |original_method, commits|
          expect(commits.map(&:id)).to eq(%w[646ece5cfed840eca0a4feb21bcd6a81bb19bda3 29284d9bcc350bcae005872d0be6edd016e2efb5 5f82584f0a907f3b30cfce5bb8df371454a90051 8a994512e8c8f0dfcf22bb16df6e876be7a61036 689600b91aabec706e657e38ea706ece1ee8268f db46a1c5a5e474aa169b6cdb7a522d891bc4c5f9])

          original_method.call(commits)
        end

        subject

        merge_request.reload
        merge_request_side_branch.reload

        expect(merge_request.merge_commit.id).to eq('646ece5cfed840eca0a4feb21bcd6a81bb19bda3')
        expect(merge_request_side_branch.merge_commit.id).to eq('29284d9bcc350bcae005872d0be6edd016e2efb5')
        # we need to use read_attribute to bypass the overridden
        # #merged_commit_sha method, which contains a fallback to
        # #merge_commit_sha
        expect(merge_request.read_attribute(:merged_commit_sha)).to eq('646ece5cfed840eca0a4feb21bcd6a81bb19bda3')
        expect(merge_request_side_branch.read_attribute(:merged_commit_sha)).to eq('29284d9bcc350bcae005872d0be6edd016e2efb5')
      end
    end
  end

  describe '#abort_ff_merge_requests_with_when_pipeline_succeeds' do
    let_it_be(:project) { create(:project, :repository) }
    let_it_be(:source_project) { project }
    let_it_be(:target_project) { project }
    let_it_be(:author) { create_user_from_membership(target_project, :developer) }
    let_it_be(:user) { create(:user) }

    let_it_be(:forked_project) do
      fork_project(target_project, author, repository: true)
    end

    let_it_be(:merge_request, refind: true) do
      create(
        :merge_request,
        author: author,
        source_project: source_project,
        source_branch: 'feature',
        target_branch: 'master',
        target_project: target_project,
        auto_merge_enabled: true,
        auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
        merge_user: user
      )
    end

    let_it_be(:newrev) do
      target_project.repository.create_file(
        user, 'test1.txt', 'Test data', message: 'Test commit', branch_name: 'master'
      )
    end

    let_it_be(:oldrev) do
      target_project
        .repository
        .commit(newrev)
        .parent_id
    end

    let(:auto_merge_strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
    let(:refresh_service) { service.new(project: project, current_user: user) }

    before do
      target_project.merge_method = merge_method
      target_project.save!
      merge_request.auto_merge_strategy = auto_merge_strategy
      merge_request.save!

      refresh_service.execute(oldrev, newrev, 'refs/heads/master')
      merge_request.reload
    end

    context 'when Project#merge_method is set to FF' do
      let(:merge_method) { :ff }

      it_behaves_like 'aborted merge requests for MWPS'

      context 'with forked project' do
        let(:source_project) { forked_project }

        it_behaves_like 'aborted merge requests for MWPS'
      end

      context 'with bogus auto merge strategy' do
        let(:auto_merge_strategy) { 'bogus' }

        it_behaves_like 'maintained merge requests for MWPS'
      end
    end

    context 'when Project#merge_method is set to rebase_merge' do
      let(:merge_method) { :rebase_merge }

      it_behaves_like 'aborted merge requests for MWPS'

      context 'with forked project' do
        let(:source_project) { forked_project }

        it_behaves_like 'aborted merge requests for MWPS'
      end
    end

    context 'when Project#merge_method is set to merge' do
      let(:merge_method) { :merge }

      it_behaves_like 'maintained merge requests for MWPS'

      context 'with forked project' do
        let(:source_project) { forked_project }

        it_behaves_like 'maintained merge requests for MWPS'
      end
    end
  end

  describe '#abort_auto_merges' do
    let_it_be(:project) { create(:project, :repository) }
    let_it_be(:user) { create(:user) }
    let_it_be(:author) { user }

    let_it_be(:merge_request, refind: true) do
      create(
        :merge_request,
        source_project: project,
        target_project: project,
        merge_user: user,
        auto_merge_enabled: true,
        auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
      )
    end

    let(:service) { described_class.new(project: project, current_user: user) }
    let(:oldrev) { merge_request.diff_refs.base_sha }
    let(:newrev) { merge_request.diff_refs.head_sha }
    let(:merge_sha) { oldrev }

    before do
      merge_request.merge_params[:sha] = merge_sha
      merge_request.save!

      service.execute(oldrev, newrev, "refs/heads/#{merge_request.source_branch}")

      merge_request.reload
    end

    it 'aborts MWPS for merge requests' do
      expect(merge_request.auto_merge_enabled?).to be_falsey
      expect(merge_request.merge_user).to be_nil
    end

    context 'when merge params contains up-to-date sha' do
      let(:merge_sha) { newrev }

      it 'maintains MWPS for merge requests' do
        expect(merge_request.auto_merge_enabled?).to be_truthy
        expect(merge_request.merge_user).to eq(user)
      end
    end
  end
end


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

module MergeRequests
  class ExecuteApprovalHooksService < MergeRequests::BaseService
    def execute(merge_request)
      # Only one approval is required for a merge request to be approved
      notification_service.async.approve_mr(merge_request, current_user)
      execute_hooks(merge_request, 'approved')
    end
  end
end

MergeRequests::ExecuteApprovalHooksService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

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

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

  describe '#execute' do
    let(:notification_service) { NotificationService.new }

    before do
      allow(service).to receive(:notification_service).and_return(notification_service)
    end
    it 'sends a notification when approving' do
      expect(notification_service).to receive_message_chain(:async, :approve_mr)
        .with(merge_request, user)

      service.execute(merge_request)
    end

    context 'with remaining approvals' do
      it 'fires an approval webhook' do
        expect(service).to receive(:execute_hooks).with(merge_request, 'approved')

        service.execute(merge_request)
      end
    end
  end
end


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

module MergeRequests
  class CreateService < MergeRequests::BaseService
    def execute
      set_projects!
      set_default_attributes!

      merge_request = MergeRequest.new
      merge_request.target_project = @project
      merge_request.source_project = @source_project
      merge_request.source_branch = params[:source_branch]

      create(merge_request)
    end

    def after_create(issuable)
      current_user_id = current_user.id

      issuable.run_after_commit do
        # Add new items to MergeRequests::AfterCreateService if they can
        # be performed in Sidekiq
        NewMergeRequestWorker.perform_async(issuable.id, current_user_id)
      end

      issuable.mark_as_preparing

      super
    end

    # expose issuable create method so it can be called from email
    # handler CreateMergeRequestHandler
    public :create

    private

    def before_create(merge_request)
      # If the fetching of the source branch occurs in an ActiveRecord
      # callback (e.g. after_create), a database transaction will be
      # open while the Gitaly RPC waits. To avoid an idle in transaction
      # timeout, we do this before we attempt to save the merge request.

      merge_request.skip_ensure_merge_request_diff = true
      merge_request.check_for_spam(user: current_user, action: :create)
    end

    def set_projects!
      # @project is used to determine whether the user can set the merge request's
      # assignee, milestone and labels. Whether they can depends on their
      # permissions on the target project.
      @source_project = @project
      @project = Project.find(params[:target_project_id]) if params[:target_project_id]

      # make sure that source/target project ids are not in
      # params so it can't be overridden later when updating attributes
      # from params when applying quick actions
      params.delete(:source_project_id)
      params.delete(:target_project_id)

      unless can?(current_user, :create_merge_request_from, @source_project) &&
          can?(current_user, :create_merge_request_in, @project)

        raise Gitlab::Access::AccessDeniedError
      end
    end

    def set_default_attributes!
      # Implemented in EE
    end
  end
end

MergeRequests::CreateService.prepend_mod_with('MergeRequests::CreateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state, feature_category: :code_review_workflow do
  include ProjectForksHelper
  include AfterNextHelpers

  let(:project) { create(:project, :repository) }
  let(:user) { create(:user) }
  let(:user2) { create(:user) }

  describe '#execute' do
    context 'valid params' do
      let(:opts) do
        {
          title: 'Awesome merge_request',
          description: 'please fix',
          source_branch: 'feature',
          target_branch: 'master',
          force_remove_source_branch: '1'
        }
      end

      let(:service) { described_class.new(project: project, current_user: user, params: opts) }
      let(:merge_request) { service.execute }

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

      it 'creates an MR' do
        expect(merge_request).to be_valid
        expect(merge_request.draft?).to be(false)
        expect(merge_request.title).to eq('Awesome merge_request')
        expect(merge_request.assignees).to be_empty
        expect(merge_request.merge_params['force_remove_source_branch']).to eq('1')
      end

      it 'does not execute hooks' do
        expect(project).not_to receive(:execute_hooks)

        service.execute
      end

      it 'refreshes the number of open merge requests', :use_clean_rails_memory_store_caching do
        expect do
          service.execute

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

      it 'creates exactly 1 create MR event', :sidekiq_inline do
        attributes = {
          action: :created,
          target_id: merge_request.id,
          target_type: merge_request.class.name
        }

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

      it 'sets the merge_status to preparing' do
        expect(merge_request.reload).to be_preparing
      end

      describe 'checking for spam' do
        it 'checks for spam' do
          expect_next_instance_of(MergeRequest) do |instance|
            expect(instance).to receive(:check_for_spam).with(user: user, action: :create)
          end

          service.execute
        end

        it 'does not persist when spam' do
          allow_next_instance_of(MergeRequest) do |instance|
            allow(instance).to receive(:spam?).and_return(true)
          end

          expect(merge_request).not_to be_persisted
        end
      end

      describe 'when marked with /draft' do
        context 'in title and in description' do
          let(:opts) do
            {
              title: 'Draft: Awesome merge_request',
              description: "well this is not done yet\n/draft",
              source_branch: 'feature',
              target_branch: 'master',
              assignees: [user2]
            }
          end

          it 'sets MR to draft' do
            expect(merge_request.draft?).to be(true)
          end
        end

        context 'in description only' do
          let(:opts) do
            {
              title: 'Awesome merge_request',
              description: "well this is not done yet\n/draft",
              source_branch: 'feature',
              target_branch: 'master',
              assignees: [user2]
            }
          end

          it 'sets MR to draft' do
            expect(merge_request.draft?).to be(true)
          end
        end
      end

      context 'when merge request is assigned to someone' do
        let(:opts) do
          {
            title: 'Awesome merge_request',
            description: 'please fix',
            source_branch: 'feature',
            target_branch: 'master',
            assignee_ids: [user2.id]
          }
        end

        it { expect(merge_request.assignees).to eq([user2]) }
      end

      context 'when reviewer is assigned' do
        let(:opts) do
          {
            title: 'Awesome merge_request',
            description: 'please fix',
            source_branch: 'feature',
            target_branch: 'master',
            reviewers: [user2]
          }
        end

        it { expect(merge_request.reviewers).to eq([user2]) }

        it 'invalidates counter cache for reviewers', :use_clean_rails_memory_store_caching do
          expect { merge_request }
            .to change { user2.review_requested_open_merge_requests_count }
            .by(1)
        end
      end

      context 'when head pipelines already exist for merge request source branch', :sidekiq_inline do
        let(:shas) { project.repository.commits(opts[:source_branch], limit: 2).map(&:id) }
        let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
        let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[0]) }
        let!(:pipeline_3) { create(:ci_pipeline, project: project, ref: "other_branch", project_id: project.id) }

        before do
          # rubocop: disable Cop/DestroyAll
          project.merge_requests
            .where(source_branch: opts[:source_branch], target_branch: opts[:target_branch])
            .destroy_all
          # rubocop: enable Cop/DestroyAll
        end

        it 'sets head pipeline' do
          merge_request = service.execute

          expect(merge_request.reload.head_pipeline).to eq(pipeline_2)
          expect(merge_request).to be_persisted
        end

        context 'when the new pipeline is associated with an old sha' do
          let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[0]) }
          let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }

          it 'sets an old pipeline with associated with the latest sha as the head pipeline' do
            merge_request = service.execute

            expect(merge_request.reload.head_pipeline).to eq(pipeline_1)
            expect(merge_request).to be_persisted
          end
        end

        context 'when there are no pipelines with the diff head sha' do
          let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
          let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }

          it 'does not set the head pipeline' do
            merge_request = service.execute

            expect(merge_request.reload.head_pipeline).to be_nil
            expect(merge_request).to be_persisted
          end
        end
      end

      describe 'Pipelines for merge requests', :sidekiq_inline do
        before do
          stub_ci_pipeline_yaml_file(config)
        end

        context "when .gitlab-ci.yml has merge_requests keywords" do
          let(:config) do
            YAML.dump({
              test: {
                stage: 'test',
                script: 'echo',
                only: ['merge_requests']
              }
            })
          end

          it 'creates a detached merge request pipeline and sets it as a head pipeline' do
            expect(merge_request).to be_persisted

            merge_request.reload
            expect(merge_request.pipelines_for_merge_request.count).to eq(1)
            expect(merge_request.actual_head_pipeline).to be_detached_merge_request_pipeline
          end

          context 'when merge request is submitted from forked project' do
            let(:target_project) { fork_project(project, nil, repository: true) }

            let(:opts) do
              {
                title: 'Awesome merge_request',
                source_branch: 'feature',
                target_branch: 'master',
                target_project_id: target_project.id
              }
            end

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

            it 'create detached merge request pipeline for fork merge request' do
              merge_request.reload

              head_pipeline = merge_request.actual_head_pipeline
              expect(head_pipeline).to be_detached_merge_request_pipeline
              expect(head_pipeline.project).to eq(target_project)
            end
          end

          context 'when there are no commits between source branch and target branch' do
            let(:opts) do
              {
                title: 'Awesome merge_request',
                description: 'please fix',
                source_branch: 'not-merged-branch',
                target_branch: 'master'
              }
            end

            it 'does not create a detached merge request pipeline' do
              expect(merge_request).to be_persisted

              merge_request.reload
              expect(merge_request.pipelines_for_merge_request.count).to eq(0)
            end
          end

          context "when branch pipeline was created before a merge request pipline has been created" do
            before do
              create(
                :ci_pipeline,
                project: merge_request.source_project,
                sha: merge_request.diff_head_sha,
                ref: merge_request.source_branch,
                tag: false
              )

              merge_request
            end

            it 'sets the latest detached merge request pipeline as the head pipeline' do
              merge_request.reload

              expect(merge_request.actual_head_pipeline).to be_merge_request_event
            end
          end
        end

        context "when .gitlab-ci.yml does not have merge_requests keywords" do
          let(:config) do
            YAML.dump({
              test: {
                stage: 'test',
                script: 'echo'
              }
            })
          end

          it 'does not create a detached merge request pipeline' do
            expect(merge_request).to be_persisted

            merge_request.reload
            expect(merge_request.pipelines_for_merge_request.count).to eq(0)
          end
        end

        context 'when .gitlab-ci.yml is invalid' do
          let(:config) { 'invalid yaml file' }

          it 'persists a pipeline with config error' do
            expect(merge_request).to be_persisted

            merge_request.reload
            expect(merge_request.pipelines_for_merge_request.count).to eq(1)
            expect(merge_request.pipelines_for_merge_request.last).to be_failed
            expect(merge_request.pipelines_for_merge_request.last).to be_config_error
          end
        end
      end

      context 'after_save callback to store_mentions' do
        let(:labels) { create_pair(:label, project: project) }
        let(:milestone) { create(:milestone, project: project) }
        let(:req_opts) { { source_branch: 'feature', target_branch: 'master' } }

        context 'when mentionable attributes change' do
          let(:opts) { { title: 'Title', description: "Description with #{user.to_reference}" }.merge(req_opts) }

          it 'saves mentions' do
            expect_next_instance_of(MergeRequest) do |instance|
              expect(instance).to receive(:store_mentions!).and_call_original
            end
            expect(merge_request.user_mentions.count).to eq 1
          end
        end

        context 'when mentionable attributes do not change' do
          let(:opts) { { label_ids: labels.map(&:id), milestone_id: milestone.id }.merge(req_opts) }

          it 'does not call store_mentions' do
            expect_next_instance_of(MergeRequest) do |instance|
              expect(instance).not_to receive(:store_mentions!).and_call_original
            end
            expect(merge_request.valid?).to be false
            expect(merge_request.user_mentions.count).to eq 0
          end
        end

        context 'when save fails' do
          let(:opts) { { label_ids: labels.map(&:id), milestone_id: milestone.id } }

          it 'does not call store_mentions' do
            expect_next_instance_of(MergeRequest) do |instance|
              expect(instance).not_to receive(:store_mentions!).and_call_original
            end
            expect(merge_request.valid?).to be false
          end
        end
      end

      context 'with a milestone' do
        let(:milestone) { create(:milestone, project: project) }

        let(:opts) { { title: 'Awesome merge_request', source_branch: 'feature', target_branch: 'master', milestone_id: milestone.id } }

        it 'deletes the cache key for milestone merge request counter' do
          expect_next(Milestones::MergeRequestsCountService, milestone)
            .to receive(:delete_cache).and_call_original

          expect(merge_request).to be_persisted
        end
      end

      it_behaves_like 'reviewer_ids filter' do
        let(:execute) { service.execute }
      end

      context 'when called in a transaction' do
        it 'does not raise an error' do
          expect { MergeRequest.transaction { described_class.new(project: project, current_user: user, params: opts).execute } }.not_to raise_error
        end
      end
    end

    it_behaves_like 'issuable record that supports quick actions' do
      let(:default_params) do
        {
          source_branch: 'feature',
          target_branch: 'master'
        }
      end

      let(:issuable) { described_class.new(project: project, current_user: user, params: params).execute }
    end

    context 'Quick actions' do
      context 'with assignee and milestone in params and command' do
        let(:merge_request) { described_class.new(project: project, current_user: user, params: opts).execute }
        let(:milestone) { create(:milestone, project: project) }

        let(:opts) do
          {
            assignee_ids: create(:user).id,
            milestone_id: 1,
            title: 'Title',
            description: %(/assign @#{user2.username}\n/milestone %"#{milestone.name}"),
            source_branch: 'feature',
            target_branch: 'master'
          }
        end

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

        it 'assigns and sets milestone to issuable from command' do
          expect(merge_request).to be_persisted
          expect(merge_request.assignees).to eq([user2])
          expect(merge_request.milestone).to eq(milestone)
        end
      end
    end

    context 'merge request create service' do
      context 'asssignee_id' do
        let(:user2) { create(:user) }

        before do
          project.add_maintainer(user)
        end

        it 'removes assignee_id when user id is invalid' do
          opts = { title: 'Title', description: 'Description', assignee_ids: [-1] }

          merge_request = described_class.new(project: project, current_user: user, params: opts).execute

          expect(merge_request.assignee_ids).to be_empty
        end

        it 'removes assignee_id when user id is 0' do
          opts = { title: 'Title', description: 'Description', assignee_ids: [0] }

          merge_request = described_class.new(project: project, current_user: user, params: opts).execute

          expect(merge_request.assignee_ids).to be_empty
        end

        it 'saves assignee when user id is valid' do
          project.add_maintainer(user2)
          opts = { title: 'Title', description: 'Description', assignee_ids: [user2.id] }

          merge_request = described_class.new(project: project, current_user: user, params: opts).execute

          expect(merge_request.assignees).to eq([user2])
        end

        context 'when assignee is set' do
          let(:opts) do
            {
              title: 'Title',
              description: 'Description',
              assignee_ids: [user2.id],
              source_branch: 'feature',
              target_branch: 'master'
            }
          end

          before do
            project.add_maintainer(user2)
          end

          it 'invalidates open merge request counter for assignees when merge request is assigned' do
            described_class.new(project: project, current_user: user, params: opts).execute

            expect(user2.assigned_open_merge_requests_count).to eq 1
          end

          it 'records the assignee assignment event', :sidekiq_inline do
            mr = described_class.new(project: project, current_user: user, params: opts).execute.reload

            expect(mr.assignment_events).to match([have_attributes(user_id: user2.id, action: 'add')])
          end
        end

        context "when issuable feature is private" do
          before do
            project.project_feature.update!(
              issues_access_level: ProjectFeature::PRIVATE,
              merge_requests_access_level: ProjectFeature::PRIVATE
            )
          end

          levels = [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC]

          levels.each do |level|
            it "removes not authorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do
              project.update!(visibility_level: level)
              opts = { title: 'Title', description: 'Description', assignee_ids: [user2.id] }

              merge_request = described_class.new(project: project, current_user: user, params: opts).execute

              expect(merge_request.assignee_id).to be_nil
            end
          end
        end
      end
    end

    shared_examples 'when source and target projects are different' do
      let(:target_project) { fork_project(project, nil, repository: true) }

      let(:opts) do
        {
          title: 'Awesome merge_request',
          source_branch: 'feature',
          target_branch: 'master',
          target_project_id: target_project.id
        }
      end

      context 'when user can not access source project' do
        before do
          target_project.add_developer(user2)
          target_project.add_maintainer(user)
        end

        it 'raises an error' do
          expect { described_class.new(project: project, current_user: user, params: opts).execute }
            .to raise_error Gitlab::Access::AccessDeniedError
        end
      end

      context 'when user can not access target project' do
        before do
          target_project.add_developer(user2)
          target_project.add_maintainer(user)
        end

        it 'raises an error' do
          expect { described_class.new(project: project, current_user: user, params: opts).execute }
            .to raise_error Gitlab::Access::AccessDeniedError
        end
      end

      context 'when the user has access to both projects' do
        before do
          target_project.add_developer(user)
          project.add_developer(user)
        end

        it 'creates the merge request', :sidekiq_inline do
          merge_request = described_class.new(project: project, current_user: user, params: opts).execute

          expect(merge_request).to be_persisted
          expect(merge_request.iid).to be > 0
          expect(merge_request.merge_request_diff).not_to be_empty
        end

        it 'does not create the merge request when the target project is archived' do
          target_project.update!(archived: true)

          expect { described_class.new(project: project, current_user: user, params: opts).execute }
            .to raise_error Gitlab::Access::AccessDeniedError
        end
      end
    end

    it_behaves_like 'when source and target projects are different'

    context 'when user sets source project id' do
      let(:another_project) { create(:project) }

      let(:opts) do
        {
          title: 'Awesome merge_request',
          source_branch: 'feature',
          target_branch: 'master',
          source_project_id: another_project.id
        }
      end

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

      it 'ignores source_project_id' do
        merge_request = described_class.new(project: project, current_user: user, params: opts).execute

        expect(merge_request.source_project_id).to eq(project.id)
      end
    end
  end
end


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

module MergeRequests
  class HandleAssigneesChangeService < MergeRequests::BaseService
    def async_execute(merge_request, old_assignees, options = {})
      MergeRequests::HandleAssigneesChangeWorker
        .perform_async(
          merge_request.id,
          current_user.id,
          old_assignees.map(&:id),
          options.stringify_keys # see: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1090
        )
    end

    def execute(merge_request, old_assignees, options = {})
      create_assignee_note(merge_request, old_assignees)
      notification_service.async.reassigned_merge_request(merge_request, current_user, old_assignees.to_a)
      Gitlab::ResourceEvents::AssignmentEventRecorder.new(parent: merge_request, old_assignees: old_assignees).record
      todo_service.reassigned_assignable(merge_request, current_user, old_assignees)

      new_assignees = merge_request.assignees - old_assignees
      merge_request_activity_counter.track_users_assigned_to_mr(users: new_assignees)
      merge_request_activity_counter.track_assignees_changed_action(user: current_user)

      execute_assignees_hooks(merge_request, old_assignees) if options['execute_hooks']
    end

    private

    def execute_assignees_hooks(merge_request, old_assignees)
      execute_hooks(
        merge_request,
        'update',
        old_associations: { assignees: old_assignees }
      )
    end
  end
end

MergeRequests::HandleAssigneesChangeService.prepend_mod_with('MergeRequests::HandleAssigneesChangeService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::HandleAssigneesChangeService, feature_category: :code_review_workflow do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:user) { create(:user) }
  let_it_be(:assignee) { create(:user) }
  let_it_be_with_reload(:merge_request) { create(:merge_request, author: user, source_project: project, assignees: [assignee]) }
  let_it_be(:old_assignees) { create_list(:user, 3) }

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

  before_all do
    project.add_maintainer(user)
    project.add_developer(assignee)

    old_assignees.each do |old_assignee|
      project.add_developer(old_assignee)
    end
  end

  describe '#async_execute' do
    def async_execute
      service.async_execute(merge_request, old_assignees, options)
    end

    it 'performs MergeRequests::HandleAssigneesChangeWorker asynchronously' do
      expect(MergeRequests::HandleAssigneesChangeWorker)
        .to receive(:perform_async)
        .with(
          merge_request.id,
          user.id,
          old_assignees.map(&:id),
          options
        )

      async_execute
    end
  end

  describe '#execute' do
    def execute
      service.execute(merge_request, old_assignees, options)
    end

    let(:note) { merge_request.notes.system.last }
    let(:removed_note) { "unassigned #{old_assignees.map(&:to_reference).to_sentence}" }

    context 'when unassigning all users' do
      before do
        merge_request.update!(assignee_ids: [])
      end

      it 'creates assignee note' do
        execute

        expect(note).not_to be_nil
        expect(note.note).to eq removed_note
      end
    end

    it 'creates assignee note' do
      execute

      expect(note).not_to be_nil
      expect(note.note).to include "assigned to #{assignee.to_reference} and #{removed_note}"
    end

    it 'sends email notifications to old and new assignees', :mailer, :sidekiq_inline do
      perform_enqueued_jobs do
        execute
      end

      should_email(assignee)
      old_assignees.each do |old_assignee|
        should_email(old_assignee)
      end
    end

    it 'creates pending todo for assignee' do
      execute

      todo = assignee.todos.last

      expect(todo).to be_pending
    end

    it 'tracks users assigned event' do
      expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
        .to receive(:track_users_assigned_to_mr).once.with(users: [assignee])

      execute
    end

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

      execute
    end

    context 'when execute_hooks option is set to true' do
      let(:options) { { 'execute_hooks' => true } }

      it 'executes hooks and integrations' do
        expect(merge_request.project).to receive(:execute_hooks).with(anything, :merge_request_hooks)
        expect(merge_request.project).to receive(:execute_integrations).with(anything, :merge_request_hooks)
        expect(service).to receive(:enqueue_jira_connect_messages_for).with(merge_request)

        execute
      end
    end
  end
end


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

module MergeRequests
  class AddContextService < MergeRequests::BaseService
    def execute
      return error("You are not allowed to access the requested resource", 403) unless current_user&.can?(:update_merge_request, merge_request)
      return error("Context commits: #{duplicates} are already created", 400) unless duplicates.empty?
      return error("One or more context commits' sha is not valid.", 400) if commits.size != commit_ids.size

      context_commit_ids = []
      MergeRequestContextCommit.transaction do
        context_commit_ids = MergeRequestContextCommit.bulk_insert(context_commit_rows, return_ids: true)
        MergeRequestContextCommitDiffFile.bulk_insert(diff_rows(context_commit_ids))
      end

      commits
    end

    private

    def raw_repository
      project.repository.raw_repository
    end

    def merge_request
      params[:merge_request]
    end

    def commit_ids
      params[:commits]
    end

    def commits
      project.repository.commits_by(oids: commit_ids)
    end

    def context_commit_rows
      @context_commit_rows ||= build_context_commit_rows(merge_request.id, commits)
    end

    def diff_rows(context_commit_ids)
      @diff_rows ||= build_diff_rows(raw_repository, commits, context_commit_ids)
    end

    def encode_in_base64?(diff_text)
      (diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?) ||
        diff_text.include?("\0")
    end

    def duplicates
      existing_oids = merge_request.merge_request_context_commits.map { |commit| commit.sha.to_s }
      existing_oids.select do |existing_oid|
        commit_ids.count { |commit_id| existing_oid.start_with?(commit_id) } > 0
      end
    end

    def build_context_commit_rows(merge_request_id, commits)
      commits.map.with_index do |commit, index|
        # generate context commit information for given commit
        commit_hash = commit.to_hash.except(:parent_ids, :referenced_by)
        sha = Gitlab::Database::ShaAttribute.serialize(commit_hash.delete(:id))
        commit_hash.merge(
          merge_request_id: merge_request_id,
          relative_order: index,
          sha: sha,
          authored_date: Gitlab::Database.sanitize_timestamp(commit_hash[:authored_date]),
          committed_date: Gitlab::Database.sanitize_timestamp(commit_hash[:committed_date]),
          trailers: Gitlab::Json.dump(commit_hash.fetch(:trailers, {}))
        )
      end
    end

    def build_diff_rows(raw_repository, commits, context_commit_ids)
      diff_rows = []
      diff_order = 0

      commits.flat_map.with_index do |commit, index|
        commit_hash = commit.to_hash.except(:parent_ids, :referenced_by)
        sha = Gitlab::Database::ShaAttribute.serialize(commit_hash.delete(:id))
        # generate context commit diff information for given commit
        diffs = commit.diffs

        compare = Gitlab::Git::Compare.new(
          raw_repository,
          diffs.diff_refs.start_sha,
          diffs.diff_refs.head_sha
        )
        compare.diffs.map do |diff|
          diff_hash = diff.to_hash.merge(
            sha: sha,
            binary: false,
            merge_request_context_commit_id: context_commit_ids[index],
            relative_order: diff_order
          )

          # Compatibility with old diffs created with Psych.
          diff_hash.tap do |hash|
            diff_text = hash[:diff]

            if encode_in_base64?(diff_text)
              hash[:binary] = true
              hash[:diff] = [diff_text].pack('m0')
            end
          end

          # Increase order for commit so when present the diffs we can use it to keep order
          diff_order += 1
          diff_rows << diff_hash
        end
      end

      diff_rows
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MergeRequests::AddContextService, feature_category: :code_review_workflow do
  let(:project) { create(:project, :repository) }
  let(:admin) { create(:admin) }
  let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: admin) }
  let(:commits) { ["874797c3a73b60d2187ed6e2fcabd289ff75171e"] }
  let(:raw_repository) { project.repository.raw }

  subject(:service) { described_class.new(project: project, current_user: admin, params: { merge_request: merge_request, commits: commits }) }

  describe "#execute" do
    context "when admin mode is enabled", :enable_admin_mode do
      it "adds context commit" do
        service.execute

        expect(merge_request.merge_request_context_commit_diff_files.length).to eq(2)
      end
    end

    context "when admin mode is disabled" do
      it "doesn't add context commit" do
        subject.execute

        expect(merge_request.merge_request_context_commit_diff_files.length).to eq(0)
      end
    end

    context "when user doesn't have permission to update merge request" do
      let(:user) { create(:user) }
      let(:merge_request1) { create(:merge_request, source_project: project, author: user) }

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

      it "doesn't add context commit" do
        subject.execute

        expect(merge_request.merge_request_context_commit_diff_files.length).to eq(0)
      end
    end

    context "when the commits array is empty" do
      subject(:service) { described_class.new(project: project, current_user: admin, params: { merge_request: merge_request, commits: [] }) }

      it "doesn't add context commit" do
        subject.execute

        expect(merge_request.merge_request_context_commit_diff_files.length).to eq(0)
      end
    end
  end
end