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

module Issues
  class CloneService < Issuable::Clone::BaseService
    CloneError = Class.new(StandardError)

    def execute(issue, target_project, with_notes: false)
      @target_project = target_project
      @with_notes = with_notes

      verify_can_clone_issue!(issue, target_project)

      super(issue, target_project)

      notify_participants

      queue_copy_designs

      new_entity
    end

    private

    attr_reader :target_project
    attr_reader :with_notes

    def verify_can_clone_issue!(issue, target_project)
      unless issue.supports_move_and_clone?
        raise CloneError, s_('CloneIssue|Cannot clone issues of \'%{issue_type}\' type.') % { issue_type: issue.issue_type }
      end

      unless issue.can_clone?(current_user, target_project)
        raise CloneError, s_('CloneIssue|Cannot clone issue due to insufficient permissions!')
      end

      if target_project.pending_delete?
        raise CloneError, s_('CloneIssue|Cannot clone issue to target project as it is pending deletion.')
      end
    end

    def update_new_entity
      # we don't call `super` because we want to be able to decide whether or not to copy all comments over.
      update_new_entity_description

      if with_notes
        copy_notes
        copy_resource_events
      end
    end

    def update_old_entity
      # no-op
      # The base_service closes the old issue, we don't want that, so we override here so nothing happens.
    end

    def create_new_entity
      new_params = {
        id: nil,
        iid: nil,
        relative_position: relative_position,
        project: target_project,
        author: current_user,
        assignee_ids: original_entity.assignee_ids
      }

      new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
      new_params = new_params.merge(rewritten_old_entity_attributes)
      new_params.delete(:created_at)
      new_params.delete(:updated_at)

      # spam checking is not necessary, as no new content is being created.

      # Skip creation of system notes for existing attributes of the issue when cloning with notes.
      # The system notes of the old issue are copied over so we don't want to end up with duplicate notes.
      # When cloning without notes, we want to generate system notes for the attributes that were copied.
      create_result = CreateService.new(
        container: target_project,
        current_user: current_user,
        params: new_params,
        perform_spam_check: false
      ).execute(skip_system_notes: with_notes)

      raise CloneError, create_result.errors.join(', ') if create_result.error? && create_result[:issue].blank?

      create_result[:issue]
    end

    def queue_copy_designs
      return unless original_entity.designs.present?

      response = DesignManagement::CopyDesignCollection::QueueService.new(
        current_user,
        original_entity,
        new_entity
      ).execute

      log_error(response.message) if response.error?
    end

    def notify_participants
      notification_service.async.issue_cloned(original_entity, new_entity, current_user)
    end

    def add_note_from
      SystemNoteService.noteable_cloned(
        new_entity,
        target_project,
        original_entity,
        current_user,
        direction: :from,
        created_at: new_entity.created_at
      )
    end

    def add_note_to
      SystemNoteService.noteable_cloned(original_entity, old_project,
        new_entity, current_user,
        direction: :to)
    end
  end
end

Issues::CloneService.prepend_mod_with('Issues::CloneService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Issues::CloneService, feature_category: :team_planning do
  include DesignManagementTestHelpers

  let_it_be(:user) { create(:user) }
  let_it_be(:author) { create(:user) }
  let_it_be(:title) { 'Some issue' }
  let_it_be(:description) { "Some issue description with mention to #{user.to_reference}" }
  let_it_be(:group) { create(:group, :private) }
  let_it_be(:sub_group_1) { create(:group, :private, parent: group) }
  let_it_be(:sub_group_2) { create(:group, :private, parent: group) }
  let_it_be(:old_project) { create(:project, namespace: sub_group_1) }
  let_it_be(:new_project) { create(:project, namespace: sub_group_2) }

  let_it_be(:old_issue, reload: true) do
    create(:issue, title: title, description: description, project: old_project, author: author)
  end

  let(:with_notes) { false }

  subject(:clone_service) do
    described_class.new(container: old_project, current_user: user)
  end

  shared_context 'user can clone issue' do
    before do
      old_project.add_reporter(user)
      new_project.add_reporter(user)
    end
  end

  describe '#execute' do
    context 'issue movable' do
      include_context 'user can clone issue'

      context 'when issue creation fails' do
        before do
          allow_next_instance_of(Issues::CreateService) do |create_service|
            allow(create_service).to receive(:execute).and_return(ServiceResponse.error(message: 'some error'))
          end
        end

        it 'raises a clone error' do
          expect { clone_service.execute(old_issue, new_project) }.to raise_error(
            Issues::CloneService::CloneError,
            'some error'
          )
        end
      end

      context 'generic issue' do
        let!(:new_issue) { clone_service.execute(old_issue, new_project, with_notes: with_notes) }

        it 'creates a new issue in the selected project' do
          expect do
            clone_service.execute(old_issue, new_project)
          end.to change { new_project.issues.count }.by(1)
        end

        it 'copies issue title' do
          expect(new_issue.title).to eq title
        end

        it 'copies issue description' do
          expect(new_issue.description).to eq description
        end

        it 'adds system note to old issue at the end' do
          expect(old_issue.notes.last.note).to start_with 'cloned to'
        end

        it 'adds system note to new issue at the start' do
          # We set an assignee so an assignee system note will be generated and
          # we can assert that the "cloned from" note is the first one
          assignee = create(:user)
          new_project.add_developer(assignee)
          old_issue.assignees = [assignee]

          new_issue = clone_service.execute(old_issue, new_project)

          expect(new_issue.notes.size).to eq(2)

          cloned_from_note = new_issue.notes.last
          expect(cloned_from_note.note).to start_with 'cloned from'
          expect(new_issue.notes.fresh.first).to eq(cloned_from_note)
        end

        it 'keeps old issue open' do
          expect(old_issue.open?).to be true
        end

        it 'persists new issue' do
          expect(new_issue.persisted?).to be true
        end

        it 'persists all changes' do
          expect(old_issue.changed?).to be false
          expect(new_issue.changed?).to be false
        end

        it 'sets the current user as author' do
          expect(new_issue.author).to eq user
        end

        it 'creates a new internal id for issue' do
          expect(new_issue.iid).to be_present
        end

        it 'sets created_at of new issue to the time of clone' do
          future_time = 5.days.from_now

          travel_to(future_time) do
            new_issue = clone_service.execute(old_issue, new_project, with_notes: with_notes)

            expect(new_issue.created_at).to be_like_time(future_time)
          end
        end

        it 'does not set moved_issue' do
          expect(old_issue.moved?).to eq(false)
        end

        context 'when copying comments' do
          let(:with_notes) { true }

          it 'does not create extra system notes' do
            new_issue = clone_service.execute(old_issue, new_project, with_notes: with_notes)

            expect(new_issue.notes.count).to eq(old_issue.notes.count)
          end
        end
      end

      context 'issue with system notes and resource events' do
        before do
          create(:note, :system, noteable: old_issue, project: old_project)
          create(:resource_label_event, label: create(:label, project: old_project), issue: old_issue)
          create(:resource_state_event, issue: old_issue, state: :reopened)
          create(:resource_milestone_event, issue: old_issue, action: 'remove', milestone_id: nil)
        end

        it 'does not copy system notes and resource events' do
          new_issue = clone_service.execute(old_issue, new_project)

          # 1 here is for the "cloned from" system note
          expect(new_issue.notes.count).to eq(1)
          expect(new_issue.resource_state_events).to be_empty
          expect(new_issue.resource_milestone_events).to be_empty
        end
      end

      context 'issue with award emoji' do
        let!(:award_emoji) { create(:award_emoji, awardable: old_issue) }

        it 'does not copy the award emoji' do
          old_issue.reload
          new_issue = clone_service.execute(old_issue, new_project)

          expect(new_issue.reload.award_emoji).to be_empty
        end
      end

      context 'issue with milestone' do
        let(:milestone) { create(:milestone, group: sub_group_1) }
        let(:new_project) { create(:project, namespace: sub_group_1) }

        let(:old_issue) do
          create(:issue, title: title, description: description, project: old_project, author: author, milestone: milestone)
        end

        it 'copies the milestone and creates a resource_milestone_event' do
          new_issue = clone_service.execute(old_issue, new_project)

          expect(new_issue.milestone).to eq(milestone)
          expect(new_issue.resource_milestone_events.count).to eq(1)
        end
      end

      context 'issue with label' do
        let(:label) { create(:group_label, group: sub_group_1) }
        let(:new_project) { create(:project, namespace: sub_group_1) }

        let(:old_issue) do
          create(:issue, project: old_project, labels: [label])
        end

        it 'copies the label and creates a resource_label_event' do
          new_issue = clone_service.execute(old_issue, new_project)

          expect(new_issue.labels).to contain_exactly(label)
          expect(new_issue.resource_label_events.count).to eq(1)
        end
      end

      context 'issue with due date' do
        let(:date) { Date.parse('2020-01-10') }
        let(:new_date) { date + 1.week }

        let(:old_issue) do
          create(:issue, title: title, description: description, project: old_project, author: author, due_date: date)
        end

        before do
          old_issue.update!(due_date: new_date)
          SystemNoteService.change_start_date_or_due_date(old_issue, old_project, author, old_issue.previous_changes.slice('due_date'))
        end

        it 'keeps the same due date' do
          new_issue = clone_service.execute(old_issue, new_project)

          expect(new_issue.due_date).to eq(old_issue.due_date)
        end
      end

      context 'issue with assignee' do
        let_it_be(:assignee) { create(:user) }

        before do
          old_issue.assignees = [assignee]
        end

        it 'preserves assignee with access to the new issue' do
          new_project.add_reporter(assignee)

          new_issue = clone_service.execute(old_issue, new_project)

          expect(new_issue.assignees).to eq([assignee])
        end

        it 'ignores assignee without access to the new issue' do
          new_issue = clone_service.execute(old_issue, new_project)

          expect(new_issue.assignees).to be_empty
        end
      end

      context 'issue is confidential' do
        before do
          old_issue.update_columns(confidential: true)
        end

        it 'preserves the confidential flag' do
          new_issue = clone_service.execute(old_issue, new_project)

          expect(new_issue.confidential).to be true
        end
      end

      context 'moving to same project' do
        it 'also works' do
          new_issue = clone_service.execute(old_issue, old_project)

          expect(new_issue.project).to eq(old_project)
          expect(new_issue.iid).not_to eq(old_issue.iid)
        end
      end

      context 'project issue hooks' do
        let!(:hook) { create(:project_hook, project: old_project, issues_events: true) }

        it 'executes project issue hooks' do
          allow_next_instance_of(WebHookService) do |instance|
            allow(instance).to receive(:execute)
          end

          # Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
          # but since the entire spec run takes place in a transaction, we never
          # actually get to the `after_commit` hook that queues these jobs.
          expect { clone_service.execute(old_issue, new_project) }
          .not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
        end
      end

      # These tests verify that notes are copied. More thorough tests are in
      # the unit test for Notes::CopyService.
      context 'issue with notes' do
        let_it_be(:notes) do
          [
            create(:note, noteable: old_issue, project: old_project, created_at: 2.weeks.ago, updated_at: 1.week.ago),
            create(:note, noteable: old_issue, project: old_project)
          ]
        end

        let(:new_issue) { clone_service.execute(old_issue, new_project, with_notes: with_notes) }

        let(:copied_notes) { new_issue.notes.limit(notes.size) } # Remove the system note added by the copy itself

        it 'does not copy notes' do
          # only the system note
          expect(copied_notes.order('id ASC').pluck(:note).size).to eq(1)
        end

        context 'when copying comments' do
          let(:with_notes) { true }

          it 'copies existing notes in order' do
            expect(copied_notes.order('id ASC').pluck(:note)).to eq(notes.map(&:note))
          end
        end
      end

      context 'issue with a design', :clean_gitlab_redis_shared_state do
        let_it_be(:new_project) { create(:project) }

        let!(:design) { create(:design, :with_lfs_file, issue: old_issue) }
        let!(:note) { create(:diff_note_on_design, noteable: design, issue: old_issue, project: old_issue.project) }
        let(:subject) { clone_service.execute(old_issue, new_project) }

        before do
          enable_design_management
        end

        it 'calls CopyDesignCollection::QueueService' do
          expect(DesignManagement::CopyDesignCollection::QueueService).to receive(:new)
                                                                            .with(user, old_issue, kind_of(Issue))
                                                                            .and_call_original

          subject
        end

        it 'logs if QueueService returns an error', :aggregate_failures do
          error_message = 'error'

          expect_next_instance_of(DesignManagement::CopyDesignCollection::QueueService) do |service|
            expect(service).to receive(:execute).and_return(
              ServiceResponse.error(message: error_message)
            )
          end
          expect(Gitlab::AppLogger).to receive(:error).with(error_message)

          subject
        end

        # Perform a small integration test to ensure the services and worker
        # can correctly create designs.
        it 'copies the design and its notes', :sidekiq_inline, :aggregate_failures do
          new_issue = subject

          expect(new_issue.designs.size).to eq(1)
          expect(new_issue.designs.first.notes.size).to eq(1)
        end
      end

      context 'issue relative position' do
        let(:subject) { clone_service.execute(old_issue, new_project) }

        it_behaves_like 'copy or reset relative position'
      end
    end

    describe 'clone permissions' do
      let(:clone) { clone_service.execute(old_issue, new_project) }

      context 'target project is pending deletion' do
        include_context 'user can clone issue'

        before do
          new_project.update_columns(pending_delete: true)
        end

        after do
          new_project.update_columns(pending_delete: false)
        end

        it { expect { clone }.to raise_error(Issues::CloneService::CloneError, /pending deletion/) }
      end

      context 'user is reporter in both projects' do
        include_context 'user can clone issue'
        it { expect { clone }.not_to raise_error }
      end

      context 'user is reporter only in new project' do
        before do
          new_project.add_reporter(user)
        end

        it { expect { clone }.to raise_error(StandardError, /permissions/) }
      end

      context 'user is reporter only in old project' do
        before do
          old_project.add_reporter(user)
        end

        it { expect { clone }.to raise_error(StandardError, /permissions/) }
      end

      context 'user is reporter in one project and guest in another' do
        before do
          new_project.add_guest(user)
          old_project.add_reporter(user)
        end

        it { expect { clone }.to raise_error(StandardError, /permissions/) }
      end

      context 'issue is not persisted' do
        include_context 'user can clone issue'
        let(:old_issue) { build(:issue, project: old_project, author: author) }

        it { expect { clone }.to raise_error(StandardError, /permissions/) }
      end
    end
  end
end


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

module Issues
  class ReorderService < Issues::BaseService
    include Gitlab::Utils::StrongMemoize

    def execute(issue)
      return false unless can?(current_user, :update_issue, issue)
      return false unless move_between_ids

      update(issue, { move_between_ids: move_between_ids })
    end

    private

    def update(issue, attrs)
      ::Issues::UpdateService.new(container: project, current_user: current_user, params: attrs).execute(issue)
    rescue ActiveRecord::RecordNotFound
      false
    end

    def move_between_ids
      strong_memoize(:move_between_ids) do
        ids = [params[:move_before_id], params[:move_after_id]]
                .map(&:to_i)
                .map { |m| m > 0 ? m : nil }

        ids.any? ? ids : nil
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Issues::ReorderService, feature_category: :team_planning do
  let_it_be(:user)    { create_default(:user) }
  let_it_be(:group)   { create(:group) }
  let_it_be(:project, reload: true) { create(:project, namespace: group) }

  shared_examples 'issues reorder service' do
    context 'when reordering issues' do
      it 'returns false with no params' do
        expect(service({}).execute(issue1)).to be_falsey
      end

      it 'returns false with both invalid params' do
        params = { move_after_id: nil, move_before_id: non_existing_record_id }

        expect(service(params).execute(issue1)).to be_falsey
      end

      it 'sorts issues' do
        params = { move_after_id: issue2.id, move_before_id: issue3.id }

        service(params).execute(issue1)

        expect(issue1.relative_position)
          .to be_between(issue2.relative_position, issue3.relative_position)
      end

      it 'sorts issues if only given one neighbour, on the left' do
        params = { move_before_id: issue3.id }

        service(params).execute(issue1)

        expect(issue1.relative_position).to be > issue3.relative_position
      end

      it 'sorts issues if only given one neighbour, on the right' do
        params = { move_after_id: issue1.id }

        service(params).execute(issue3)

        expect(issue3.relative_position).to be < issue1.relative_position
      end
    end
  end

  describe '#execute' do
    let_it_be(:issue1, reload: true) { create(:issue, project: project, relative_position: 10) }
    let_it_be(:issue2) { create(:issue, project: project, relative_position: 20) }
    let_it_be(:issue3, reload: true) { create(:issue, project: project, relative_position: 30) }

    context 'when ordering issues in a project' do
      before do
        project.add_developer(user)
      end

      it_behaves_like 'issues reorder service'
    end

    context 'when ordering issues in a group' do
      before do
        group.add_developer(user)
      end

      it_behaves_like 'issues reorder service'

      context 'when ordering in a group issue list' do
        let(:params) { { move_after_id: issue2.id, move_before_id: issue3.id } }

        subject { service(params) }

        it 'sorts issues' do
          project2 = create(:project, namespace: group)
          issue4   = create(:issue, project: project2)

          subject.execute(issue4)

          expect(issue4.relative_position)
            .to be_between(issue2.relative_position, issue3.relative_position)
        end
      end
    end
  end

  def service(params)
    described_class.new(container: project, current_user: user, params: params)
  end
end


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

module Issues
  class PrepareImportCsvService < Import::PrepareService
    extend ::Gitlab::Utils::Override

    private

    override :worker
    def worker
      ImportIssuesCsvWorker
    end

    override :success_message
    def success_message
      _("Your issues are being imported. Once finished, you'll get a confirmation email.")
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Issues::PrepareImportCsvService, feature_category: :team_planning do
  let_it_be(:project) { create(:project) }
  let_it_be(:user) { create(:user) }

  let(:file) { double }
  let(:upload_service) { double }
  let(:uploader) { double }
  let(:upload) { double }

  let(:subject) do
    described_class.new(project, user, file: file).execute
  end

  context 'when file is uploaded correctly' do
    let(:upload_id) { 99 }

    before do
      mock_upload
    end

    it 'returns a success message' do
      result = subject

      expect(result[:status]).to eq(:success)
      expect(result[:message]).to eq("Your issues are being imported. Once finished, you'll get a confirmation email.")
    end

    it 'enqueues the ImportRequirementsCsvWorker' do
      expect(ImportIssuesCsvWorker).to receive(:perform_async).with(user.id, project.id, upload_id)

      subject
    end
  end

  context 'when file upload fails' do
    before do
      mock_upload(false)
    end

    it 'returns an error message' do
      result = subject

      expect(result[:status]).to eq(:error)
      expect(result[:message]).to eq('File upload error.')
    end
  end
end


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

module Issues
  class ImportCsvService < Issuable::ImportCsv::BaseService
    def execute
      record_import_attempt

      super
    end

    def email_results_to_user
      Notify.import_issues_csv_email(user.id, project.id, results).deliver_later
    end

    private

    def create_object(attributes)
      super[:issue]
    end

    def create_object_class
      Issues::CreateService
    end

    def extra_create_service_params
      { perform_spam_check: perform_spam_check? }
    end

    def perform_spam_check?
      !user.can_admin_all_resources?
    end

    def record_import_attempt
      Issues::CsvImport.create!(user: user, project: project)
    end
  end
end

Issues::ImportCsvService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Issues::ImportCsvService, feature_category: :team_planning do
  let(:project) { create(:project) }
  let(:user) { create(:user) }
  let(:assignee) { create(:user, username: 'csv_assignee') }
  let(:file) { fixture_file_upload('spec/fixtures/csv_complex.csv') }
  let(:service) do
    uploader = FileUploader.new(project)
    uploader.store!(file)

    described_class.new(user, project, uploader)
  end

  let!(:test_milestone) { create(:milestone, project: project, title: '15.10') }

  include_examples 'issuable import csv service', 'issue' do
    let(:issuables) { project.issues }
    let(:email_method) { :import_issues_csv_email }
  end

  describe '#execute' do
    subject { service.execute }

    it_behaves_like 'performs a spam check', true

    it 'sets all issueable attributes and executes quick actions' do
      project.add_developer(user)
      project.add_developer(assignee)

      expect { subject }.to change { issuables.count }.by 3

      expect(issuables.reload).to include(
        have_attributes(
          title: 'Title with quote"',
          description: 'Description',
          time_estimate: 3600,
          assignees: include(assignee),
          due_date: Date.new(2022, 6, 28),
          milestone_id: test_milestone.id
        )
      )
    end

    context 'when user is an admin' do
      before do
        allow(user).to receive(:can_admin_all_resources?).and_return(true)
      end

      it_behaves_like 'performs a spam check', false
    end
  end
end


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

module Issues
  class BuildService < Issues::BaseService
    include ResolveDiscussions

    def execute(initialize_callbacks: true)
      filter_resolve_discussion_params

      container_param = case container
                        when Project
                          { project: project }
                        when Namespaces::ProjectNamespace
                          { project: container.project }
                        else
                          { namespace: container }
                        end

      @issue = model_klass.new(issue_params.merge(container_param)).tap do |issue|
        set_work_item_type(issue)
        initialize_callbacks!(issue) if initialize_callbacks
      end
    end

    def issue_params_with_info_from_discussions
      return {} unless merge_request_to_resolve_discussions_of

      { title: title_from_merge_request, description: description_for_discussions }
    end

    def title_from_merge_request
      "Follow-up from \"#{merge_request_to_resolve_discussions_of.title}\""
    end

    def description_for_discussions
      if discussions_to_resolve.empty?
        return "There are no unresolved discussions. "\
               "Review the conversation in #{merge_request_to_resolve_discussions_of.to_reference}"
      end

      description = "The following #{'discussion'.pluralize(discussions_to_resolve.size)} "\
                    "from #{merge_request_to_resolve_discussions_of.to_reference} "\
                    "should be addressed:"

      [description, *items_for_discussions].join("\n\n")
    end

    def items_for_discussions
      discussions_to_resolve.map { |discussion| item_for_discussion(discussion) }
    end

    def item_for_discussion(discussion)
      first_note_to_resolve = discussion.first_note_to_resolve || discussion.first_note

      is_very_first_note = first_note_to_resolve == discussion.first_note
      action = is_very_first_note ? "started" : "commented on"

      note_url = Gitlab::UrlBuilder.build(first_note_to_resolve)

      other_note_count = discussion.notes.size - 1

      discussion_info = ["- [ ] #{first_note_to_resolve.author.to_reference} #{action} a [discussion](#{note_url}): "]
      discussion_info << "(+#{other_note_count} #{'comment'.pluralize(other_note_count)})" if other_note_count > 0

      note_without_block_quotes = Banzai::Filter::BlockquoteFenceFilter.new(first_note_to_resolve.note).call
      spaces = ' ' * 4
      quote = note_without_block_quotes.lines.map { |line| "#{spaces}> #{line}" }.join

      [discussion_info.join(' '), quote].join("\n\n")
    end

    def issue_params
      @issue_params ||= build_issue_params
    end

    private

    def set_work_item_type(issue)
      work_item_type = if params[:work_item_type_id].present?
                         params.delete(:work_item_type)
                         WorkItems::Type.find_by(id: params.delete(:work_item_type_id)) # rubocop: disable CodeReuse/ActiveRecord
                       else
                         params.delete(:work_item_type)
                       end

      # We need to support the legacy input params[:issue_type] even if we don't have the issue_type column anymore.
      # In the future only params[:work_item_type] should be provided
      base_type = work_item_type&.base_type || params[:issue_type]

      issue.work_item_type = if create_issue_type_allowed?(container, base_type)
                               work_item_type || WorkItems::Type.default_by_type(base_type)
                             else
                               # If no work item type was provided or not allowed, we need to set it to
                               # the default issue_type
                               WorkItems::Type.default_by_type(::Issue::DEFAULT_ISSUE_TYPE)
                             end
    end

    def model_klass
      ::Issue
    end

    def public_params
      # Additional params may be assigned later (in a CreateService for example)
      public_issue_params = [
        :title,
        :description,
        :confidential
      ]

      params.slice(*public_issue_params)
    end

    def build_issue_params
      { author: current_user }
        .merge(issue_params_with_info_from_discussions)
        .merge(public_params)
        .with_indifferent_access
    end
  end
end

Issues::BuildService.prepend_mod_with('Issues::BuildService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Issues::BuildService, feature_category: :team_planning do
  using RSpec::Parameterized::TableSyntax

  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:developer) { create(:user) }
  let_it_be(:reporter) { create(:user) }
  let_it_be(:guest) { create(:user) }

  let(:user) { developer }

  before_all do
    project.add_developer(developer)
    project.add_reporter(reporter)
    project.add_guest(guest)
  end

  def build_issue(issue_params = {})
    described_class.new(container: project, current_user: user, params: issue_params).execute
  end

  context 'for a single discussion' do
    describe '#execute' do
      let(:merge_request) { create(:merge_request, title: "Hello world", source_project: project) }
      let(:discussion) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, note: "Almost done").to_discussion }

      subject { build_issue(merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) }

      it 'references the noteable title in the issue title' do
        expect(subject.title).to include('Hello world')
      end

      it 'adds the note content to the description' do
        expect(subject.description).to include('Almost done')
      end
    end
  end

  context 'for discussions in a merge request' do
    let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) }

    describe '#items_for_discussions' do
      it 'has an item for each discussion' do
        create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.source_project, line_number: 13)
        service = described_class.new(container: project, current_user: user, params: { merge_request_to_resolve_discussions_of: merge_request.iid })

        service.execute

        expect(service.items_for_discussions.size).to eq(2)
      end
    end

    describe '#item_for_discussion' do
      let(:service) { described_class.new(container: project, current_user: user, params: { merge_request_to_resolve_discussions_of: merge_request.iid }) }

      it 'mentions the author of the note' do
        discussion = create(:diff_note_on_merge_request, author: create(:user, username: 'author')).to_discussion
        expect(service.item_for_discussion(discussion)).to include('@author')
      end

      it 'wraps the note in a blockquote' do
        note_text = "This is a string\n"\
                    "\n"\
                    ">>>\n"\
                    "with a blockquote\n"\
                    "> That has a quote\n"\
                    ">>>\n"
        note_result = "    > This is a string\n"\
                      "    > \n"\
                      "    > \n"\
                      "    > > with a blockquote\n"\
                      "    > > > That has a quote\n"\
                      "    > \n"
        discussion = create(:diff_note_on_merge_request, note: note_text).to_discussion
        expect(service.item_for_discussion(discussion)).to include(note_result)
      end
    end

    describe '#execute' do
      let(:base_params) { { merge_request_to_resolve_discussions_of: merge_request.iid } }

      context 'without additional params' do
        subject { build_issue(base_params) }

        it 'has the merge request reference in the title' do
          expect(subject.title).to include(merge_request.title)
        end

        it 'has the reference of the merge request in the description' do
          expect(subject.description).to include(merge_request.to_reference)
        end
      end

      it 'uses provided title if title param given' do
        issue = build_issue(base_params.merge(title: 'What an issue'))

        expect(issue.title).to eq('What an issue')
      end

      it 'uses provided description if description param given' do
        issue = build_issue(base_params.merge(description: 'Fix at your earliest convenience'))

        expect(issue.description).to eq('Fix at your earliest convenience')
      end

      describe 'with multiple discussions' do
        let!(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.target_project, line_number: 15) }

        it 'mentions all the authors in the description' do
          authors = merge_request.resolvable_discussions.map(&:author)

          expect(build_issue(base_params).description).to include(*authors.map(&:to_reference))
        end

        it 'has a link for each unresolved discussion in the description' do
          notes = merge_request.resolvable_discussions.map(&:first_note)
          links = notes.map { |note| Gitlab::UrlBuilder.build(note) }

          expect(build_issue(base_params).description).to include(*links)
        end

        it 'mentions additional notes' do
          create_list(:diff_note_on_merge_request, 2, noteable: merge_request, project: merge_request.target_project, in_reply_to: diff_note)

          expect(build_issue(base_params).description).to include('(+2 comments)')
        end
      end
    end
  end

  context 'For a merge request without discussions' do
    let(:merge_request) { create(:merge_request, source_project: project) }

    describe '#execute' do
      it 'mentions the merge request in the description' do
        issue = build_issue(merge_request_to_resolve_discussions_of: merge_request.iid)

        expect(issue.description).to include("Review the conversation in #{merge_request.to_reference}")
      end
    end
  end

  describe '#execute' do
    describe 'setting milestone' do
      context 'when developer' do
        it 'builds a new issues with given params' do
          milestone = create(:milestone, project: project)
          issue = build_issue(milestone_id: milestone.id)

          expect(issue.milestone).to eq(milestone)
        end

        it 'sets milestone to nil if it is not available for the project' do
          milestone = create(:milestone, project: create(:project))
          issue = build_issue(milestone_id: milestone.id)

          expect(issue.milestone).to be_nil
        end
      end

      context 'when user is not a project member' do
        let(:user) { create(:user) }

        it 'cannot set milestone' do
          milestone = create(:milestone, project: project)
          issue = build_issue(milestone_id: milestone.id)

          expect(issue.milestone).to be_nil
        end
      end
    end

    describe 'setting issue type' do
      context 'with a corresponding WorkItems::Type' do
        let_it_be(:type_task) { WorkItems::Type.default_by_type(:task) }
        let_it_be(:type_task_id) { type_task.id }
        let_it_be(:type_issue_id) { WorkItems::Type.default_issue_type.id }
        let_it_be(:type_incident_id) { WorkItems::Type.default_by_type(:incident).id }
        let(:combined_params) { { work_item_type: type_task, issue_type: 'issue' } }
        let(:work_item_params) { { work_item_type_id: type_task_id } }

        where(:issue_params, :current_user, :work_item_type_id, :resulting_issue_type) do
          { issue_type: nil }           | ref(:guest)    | ref(:type_issue_id)    | 'issue'
          { issue_type: 'issue' }       | ref(:guest)    | ref(:type_issue_id)    | 'issue'
          { issue_type: 'incident' }    | ref(:guest)    | ref(:type_issue_id)    | 'issue'
          { issue_type: 'incident' }    | ref(:reporter) | ref(:type_incident_id) | 'incident'
          ref(:combined_params)         | ref(:reporter) | ref(:type_task_id)     | 'task'
          ref(:work_item_params)        | ref(:reporter) | ref(:type_task_id)     | 'task'
          # update once support for test_case is enabled
          { issue_type: 'test_case' }   | ref(:guest)    | ref(:type_issue_id)    | 'issue'
          # update once support for requirement is enabled
          { issue_type: 'requirement' } | ref(:guest)    | ref(:type_issue_id)    | 'issue'
          { issue_type: 'invalid' }     | ref(:guest)    | ref(:type_issue_id)    | 'issue'
          # ensure that we don't set a value which has a permission check but is an invalid issue type
          { issue_type: 'project' }     | ref(:guest)    | ref(:type_issue_id)    | 'issue'
        end

        with_them do
          let(:user) { current_user }

          it 'builds an issue' do
            issue = build_issue(**issue_params)

            expect(issue.work_item_type_id).to eq(work_item_type_id)
          end
        end
      end
    end
  end
end


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

module Issues
  class CloseService < Issues::BaseService
    # Closes the supplied issue if the current user is able to do so.
    def execute(issue, commit: nil, notifications: true, system_note: true, skip_authorization: false)
      return issue unless can_close?(issue, skip_authorization: skip_authorization)

      close_issue(
        issue,
        closed_via: commit,
        notifications: notifications,
        system_note: system_note
      )
    end

    # Closes the supplied issue without checking if the user is authorized to
    # do so.
    #
    # The code calling this method is responsible for ensuring that a user is
    # allowed to close the given issue.
    def close_issue(issue, closed_via: nil, notifications: true, system_note: true)
      if issue.is_a?(ExternalIssue)
        close_external_issue(issue, closed_via)

        return issue
      end

      if issue.close(current_user)
        event_service.close_issue(issue, current_user)
        create_note(issue, closed_via) if system_note

        if current_user.project_bot?
          log_audit_event(issue, current_user, "#{issue.issue_type}_closed_by_project_bot",
            "Closed #{issue.issue_type.humanize(capitalize: false)} #{issue.title}")
        end

        closed_via = _("commit %{commit_id}") % { commit_id: closed_via.id } if closed_via.is_a?(Commit)

        notification_service.async.close_issue(issue, current_user, { closed_via: closed_via }) if notifications
        todo_service.close_issue(issue, current_user)
        perform_incident_management_actions(issue)
        execute_hooks(issue, 'close')
        invalidate_cache_counts(issue, users: issue.assignees)
        issue.update_project_counter_caches
        track_incident_action(current_user, issue, :incident_closed)

        if closed_via.is_a?(MergeRequest)
          store_first_mentioned_in_commit_at(issue, closed_via)
          Onboarding::ProgressService.new(project.namespace).execute(action: :issue_auto_closed)
        end

        Milestones::ClosedIssuesCountService.new(issue.milestone).delete_cache if issue.milestone
      end

      issue
    end

    private

    def can_close?(issue, skip_authorization: false)
      skip_authorization || can?(current_user, :update_issue, issue) || issue.is_a?(ExternalIssue)
    end

    def perform_incident_management_actions(issue)
      resolve_alerts(issue)
      resolve_incident(issue)
    end

    def close_external_issue(issue, closed_via)
      return unless project.external_issue_tracker&.support_close_issue?

      project.external_issue_tracker.close_issue(closed_via, issue, current_user)
      todo_service.close_issue(issue, current_user)
    end

    def create_note(issue, current_commit)
      SystemNoteService.change_status(issue, issue.project, current_user, issue.state, current_commit)
    end

    def resolve_alerts(issue)
      issue.alert_management_alerts.each { |alert| resolve_alert(alert) }
    end

    def resolve_alert(alert)
      return if alert.resolved?

      issue = alert.issue

      if alert.resolve
        SystemNoteService.change_alert_status(alert, Users::Internal.alert_bot, " because #{current_user.to_reference} closed incident #{issue.to_reference(project)}")
      else
        Gitlab::AppLogger.warn(
          message: 'Cannot resolve an associated Alert Management alert',
          issue_id: issue.id,
          alert_id: alert.id,
          alert_errors: alert.errors.messages
        )
      end
    end

    def resolve_incident(issue)
      return unless issue.work_item_type&.incident?

      status = issue.incident_management_issuable_escalation_status || issue.build_incident_management_issuable_escalation_status

      return unless status.resolve

      SystemNoteService.change_incident_status(issue, current_user, ' by closing the incident')
      IncidentManagement::TimelineEvents::CreateService.resolve_incident(issue, current_user)
    end

    def store_first_mentioned_in_commit_at(issue, merge_request, max_commit_lookup: 100)
      metrics = issue.metrics
      return if metrics.nil? || metrics.first_mentioned_in_commit_at

      first_commit_timestamp = merge_request.commits(limit: max_commit_lookup).last.try(:authored_date)
      return unless first_commit_timestamp

      metrics.update!(first_mentioned_in_commit_at: first_commit_timestamp)
    end
  end
end

Issues::CloseService.prepend_mod_with('Issues::CloseService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Issues::CloseService, feature_category: :team_planning do
  let(:project) { create(:project, :repository) }
  let(:delegated_project) { project.project_namespace.project }
  let(:user) { create(:user, email: "user@example.com") }
  let(:user2) { create(:user, email: "user2@example.com") }
  let(:guest) { create(:user) }
  let(:issue) { create(:issue, title: "My issue", project: project, assignees: [user2], author: create(:user)) }
  let(:external_issue) { ExternalIssue.new('JIRA-123', project) }
  let(:closing_merge_request) { create(:merge_request, source_project: project) }
  let(:closing_commit) { create(:commit, project: project) }
  let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }

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

  describe '#execute' do
    let(:service) { described_class.new(container: project, current_user: user) }

    context 'when skip_authorization is true' do
      it 'does close the issue even if user is not authorized' do
        non_authorized_user = create(:user)

        service = described_class.new(container: project, current_user: non_authorized_user)

        expect do
          service.execute(issue, skip_authorization: true)
        end.to change { issue.reload.state }.from('opened').to('closed')
      end
    end

    it 'checks if the user is authorized to update the issue' do
      expect(service).to receive(:can?).with(user, :update_issue, issue)
        .and_call_original

      service.execute(issue)
    end

    it 'does not close the issue when the user is not authorized to do so' do
      allow(service).to receive(:can?).with(user, :update_issue, issue)
        .and_return(false)

      expect(service).not_to receive(:close_issue)
      expect(service.execute(issue)).to eq(issue)
    end

    it 'closes the external issue even when the user is not authorized to do so' do
      allow(service).to receive(:can?).with(user, :update_issue, external_issue)
        .and_return(false)

      expect(service).to receive(:close_issue)
        .with(external_issue, closed_via: nil, notifications: true, system_note: true)

      service.execute(external_issue)
    end

    it 'closes the issue when the user is authorized to do so' do
      allow(service).to receive(:can?).with(user, :update_issue, issue)
        .and_return(true)

      expect(service).to receive(:close_issue)
        .with(issue, closed_via: nil, notifications: true, system_note: true)

      service.execute(issue)
    end

    it 'refreshes the number of open issues', :use_clean_rails_memory_store_caching do
      expect do
        service.execute(issue)

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

    it 'invalidates counter cache for assignees' do
      expect_any_instance_of(User).to receive(:invalidate_issue_cache_counts)

      service.execute(issue)
    end

    it 'does not change escalation status' do
      resolved = IncidentManagement::Escalatable::STATUSES[:resolved]

      expect { service.execute(issue) }
        .to not_change { IncidentManagement::IssuableEscalationStatus.where(issue: issue).count }
        .and not_change { IncidentManagement::IssuableEscalationStatus.where(status: resolved).count }
    end

    context 'issue is incident type' do
      let(:issue) { create(:incident, project: project) }
      let(:current_user) { user }

      subject { service.execute(issue) }

      it_behaves_like 'an incident management tracked event', :incident_management_incident_closed

      it_behaves_like 'Snowplow event tracking with RedisHLL context' do
        let(:namespace) { issue.namespace }
        let(:category) { described_class.to_s }
        let(:action) { 'incident_management_incident_closed' }
        let(:label) { 'redis_hll_counters.incident_management.incident_management_total_unique_counts_monthly' }
      end

      it 'creates a new escalation resolved escalation status', :aggregate_failures do
        expect { service.execute(issue) }.to change { IncidentManagement::IssuableEscalationStatus.where(issue: issue).count }.by(1)

        expect(issue.incident_management_issuable_escalation_status).to be_resolved
      end

      context 'when there is an escalation status' do
        before do
          create(:incident_management_issuable_escalation_status, issue: issue)
        end

        it 'changes escalations status to resolved' do
          expect { service.execute(issue) }.to change { issue.incident_management_issuable_escalation_status.reload.resolved? }.to(true)
        end

        it 'adds a system note', :aggregate_failures do
          expect { service.execute(issue) }.to change { issue.notes.count }.by(1)

          new_note = issue.notes.last
          expect(new_note.note).to eq('changed the incident status to **Resolved** by closing the incident')
          expect(new_note.author).to eq(user)
        end

        it 'adds a timeline event', :aggregate_failures do
          expect(IncidentManagement::TimelineEvents::CreateService)
            .to receive(:resolve_incident)
            .with(issue, user)
            .and_call_original

          expect { service.execute(issue) }.to change { issue.incident_management_timeline_events.count }.by(1)
        end

        context 'when the escalation status did not change to resolved' do
          let(:escalation_status) { instance_double('IncidentManagement::IssuableEscalationStatus', resolve: false, status_name: 'acknowledged') }

          before do
            allow(issue).to receive(:incident_management_issuable_escalation_status).and_return(escalation_status)
          end

          it 'does not create a system note' do
            expect { service.execute(issue) }.not_to change { issue.notes.count }
          end

          it 'does not create a timeline event' do
            expect { service.execute(issue) }.not_to change { issue.incident_management_timeline_events.count }
          end
        end
      end
    end
  end

  describe '#close_issue' do
    context 'with external issue' do
      context 'with an active external issue tracker supporting close_issue' do
        let!(:external_issue_tracker) { create(:jira_integration, project: project) }

        it 'closes the issue on the external issue tracker' do
          project.reload
          expect(project.external_issue_tracker).to receive(:close_issue)

          described_class.new(container: project, current_user: user).close_issue(external_issue)
        end
      end

      context 'with inactive external issue tracker supporting close_issue' do
        let!(:external_issue_tracker) { create(:jira_integration, project: project, active: false) }

        it 'does not close the issue on the external issue tracker' do
          project.reload
          expect(project.external_issue_tracker).not_to receive(:close_issue)

          described_class.new(container: project, current_user: user).close_issue(external_issue)
        end
      end

      context 'with an active external issue tracker not supporting close_issue' do
        let!(:external_issue_tracker) { create(:bugzilla_integration, project: project) }

        it 'does not close the issue on the external issue tracker' do
          project.reload
          expect(project.external_issue_tracker).not_to receive(:close_issue)

          described_class.new(container: project, current_user: user).close_issue(external_issue)
        end
      end
    end

    context "closed by a merge request" do
      subject(:close_issue) do
        perform_enqueued_jobs do
          described_class.new(container: project, current_user: user).close_issue(issue, closed_via: closing_merge_request)
        end
      end

      it 'mentions closure via a merge request' do
        expect_next_instance_of(NotificationService::Async) do |service|
          expect(service).to receive(:close_issue).with(issue, user, { closed_via: closing_merge_request })
        end

        close_issue
      end

      it_behaves_like 'records an onboarding progress action', :issue_auto_closed do
        let(:namespace) { project.namespace }
      end

      context 'updating `metrics.first_mentioned_in_commit_at`' do
        context 'when `metrics.first_mentioned_in_commit_at` is not set' do
          it 'uses the first commit authored timestamp' do
            expected = closing_merge_request.commits.take(100).last.authored_date

            close_issue

            expect(issue.metrics.first_mentioned_in_commit_at).to eq(expected)
          end
        end

        context 'when `metrics.first_mentioned_in_commit_at` is already set' do
          before do
            issue.metrics.update!(first_mentioned_in_commit_at: Time.current)
          end

          it 'does not update the metrics' do
            expect { close_issue }.not_to change { issue.metrics.first_mentioned_in_commit_at }
          end
        end

        context 'when merge request has no commits' do
          let(:closing_merge_request) { create(:merge_request, :without_diffs, source_project: project) }

          it 'does not update the metrics' do
            close_issue

            expect(issue.metrics.first_mentioned_in_commit_at).to be_nil
          end
        end
      end
    end

    context "closed by a commit", :sidekiq_might_not_need_inline do
      it 'mentions closure via a commit' do
        expect_next_instance_of(NotificationService::Async) do |service|
          expect(service).to receive(:close_issue).with(issue, user, { closed_via: "commit #{closing_commit.id}" })
        end

        described_class.new(container: project, current_user: user).close_issue(issue, closed_via: closing_commit)
      end
    end

    context "valid params" do
      subject(:close_issue) do
        perform_enqueued_jobs do
          described_class.new(container: project, current_user: user).close_issue(issue)
        end
      end

      it 'verifies the number of queries' do
        recorded = ActiveRecord::QueryRecorder.new { close_issue }
        expected_queries = 30

        expect(recorded.count).to be <= expected_queries
        expect(recorded.cached_count).to eq(0)
      end

      it 'closes the issue' do
        close_issue

        expect(issue).to be_valid
        expect(issue).to be_closed
      end

      it 'records closed user' do
        close_issue

        expect(issue.reload.closed_by_id).to be(user.id)
      end

      it 'sends notification', :sidekiq_might_not_need_inline do
        expect_next_instance_of(NotificationService::Async) do |service|
          expect(service).to receive(:close_issue).with(issue, user, { closed_via: nil })
        end

        close_issue
      end

      it 'creates resource state event about the issue being closed' do
        close_issue

        event = issue.resource_state_events.last
        expect(event.state).to eq('closed')
      end

      it 'marks todos as done' do
        close_issue

        expect(todo.reload).to be_done
      end

      context 'when closing the issue fails' do
        it 'does not assign a closed_by value for the issue' do
          allow(issue).to receive(:close).and_return(false)

          close_issue

          expect(issue.closed_by_id).to be_nil
        end
      end

      context 'when there is an associated Alert Management Alert' do
        context 'when alert can be resolved' do
          it 'resolves an alert and sends a system note' do
            alert = create(:alert_management_alert, issue: issue, project: project)

            expect(SystemNoteService).to receive(:change_alert_status)
              .with(alert, Users::Internal.alert_bot, " because #{user.to_reference} closed incident #{issue.to_reference(project)}")

            close_issue

            expect(alert.reload).to be_resolved
          end
        end

        context 'when alert cannot be resolved' do
          before do
            allow(Gitlab::AppLogger).to receive(:warn).and_call_original
          end

          it 'writes a warning into the log' do
            alert = create(:alert_management_alert, :with_validation_errors, issue: issue, project: project)

            close_issue

            expect(Gitlab::AppLogger).to have_received(:warn).with(
              message: 'Cannot resolve an associated Alert Management alert',
              issue_id: issue.id,
              alert_id: alert.id,
              alert_errors: { hosts: ['hosts array is over 255 chars'] }
            )
          end
        end
      end

      context 'when there are several associated Alert Management Alerts' do
        context 'when alerts can be resolved' do
          it 'resolves an alert and sends a system note', :aggregate_failures do
            alerts = create_list(:alert_management_alert, 2, issue: issue, project: project)

            alerts.each do |alert|
              expect(SystemNoteService).to receive(:change_alert_status)
                .with(alert, Users::Internal.alert_bot, " because #{user.to_reference} closed incident #{issue.to_reference(project)}")
            end

            close_issue

            expect(alerts.map(&:reload)).to all(be_resolved)
          end
        end
      end

      it 'deletes milestone issue counters cache' do
        issue.update!(milestone: create(:milestone, project: project))

        expect_next_instance_of(Milestones::ClosedIssuesCountService, issue.milestone) do |service|
          expect(service).to receive(:delete_cache).and_call_original
        end

        close_issue
      end

      it_behaves_like 'does not record an onboarding progress action'
    end

    context 'when issue is not confidential' do
      let(:expected_payload) do
        include(
          event_type: 'issue',
          object_kind: 'issue',
          changes: {
            closed_at: { current: kind_of(Time), previous: nil },
            state_id: { current: 2, previous: 1 },
            updated_at: { current: kind_of(Time), previous: kind_of(Time) }
          },
          object_attributes: include(
            closed_at: kind_of(Time),
            state: 'closed',
            action: 'close'
          )
        )
      end

      it 'executes issue hooks' do
        expect(delegated_project).to receive(:execute_hooks).with(expected_payload, :issue_hooks)
        expect(delegated_project).to receive(:execute_integrations).with(expected_payload, :issue_hooks)

        described_class.new(container: delegated_project, current_user: user).close_issue(issue)
      end
    end

    context 'when issue is confidential' do
      it 'executes confidential issue hooks' do
        issue = create(:issue, :confidential, project: project)

        expect(delegated_project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
        expect(delegated_project).to receive(:execute_integrations).with(an_instance_of(Hash), :confidential_issue_hooks)

        described_class.new(container: project, current_user: user).close_issue(issue)
      end
    end

    context 'internal issues disabled' do
      before do
        project.issues_enabled = false
        project.save!
      end

      it 'does not close the issue' do
        expect(issue).to be_valid
        expect(issue).to be_opened
        expect(todo.reload).to be_pending
      end
    end
  end
end


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

module Issues
  class AfterCreateService < Issues::BaseService
    def execute(issue)
      todo_service.new_issue(issue, current_user)
      track_incident_action(current_user, issue, :incident_created)
    end
  end
end

Issues::AfterCreateService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Issues::AfterCreateService, feature_category: :team_planning do
  include AfterNextHelpers

  let_it_be(:project) { create(:project) }
  let_it_be(:current_user) { create(:user) }
  let_it_be(:assignee) { create(:user) }
  let_it_be(:milestone) { create(:milestone, project: project) }
  let_it_be(:issue) { create(:issue, project: project, author: current_user, milestone: milestone, assignee_ids: [assignee.id]) }

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

  describe '#execute' do
    it 'creates a pending todo for new assignee' do
      attributes = {
        project: project,
        author: current_user,
        user: assignee,
        target_id: issue.id,
        target_type: issue.class.name,
        action: Todo::ASSIGNED,
        state: :pending
      }

      expect { after_create_service.execute(issue) }.to change { Todo.where(attributes).count }.by(1)
    end

    context 'with a regular issue' do
      it_behaves_like 'does not track incident management event', :incident_management_incident_created do
        subject { after_create_service.execute(issue) }
      end
    end

    context 'with an incident issue' do
      let(:issue) { create(:issue, :incident, project: project, author: current_user) }

      it_behaves_like 'an incident management tracked event', :incident_management_incident_created do
        subject { after_create_service.execute(issue) }
      end
    end
  end
end


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

module Issues
  class ZoomLinkService < Issues::BaseService
    def initialize(container:, current_user:, params:)
      super

      @issue = params.fetch(:issue)
      @added_meeting = ZoomMeeting.canonical_meeting(@issue)
    end

    def add_link(link)
      if can_add_link? && (link = parse_link(link))
        begin
          add_zoom_meeting(link)
        rescue ActiveRecord::RecordNotUnique
          error(message: _('Failed to add a Zoom meeting'))
        end
      else
        error(message: _('Failed to add a Zoom meeting'))
      end
    end

    def remove_link
      if can_remove_link?
        remove_zoom_meeting
        success(message: _('Zoom meeting removed'))
      else
        error(message: _('Failed to remove a Zoom meeting'))
      end
    end

    def can_add_link?
      can_change_link? && !@added_meeting
    end

    def can_remove_link?
      can_change_link? && @issue.persisted? && !!@added_meeting
    end

    def parse_link(link)
      Gitlab::ZoomLinkExtractor.new(link).links.last
    end

    private

    attr_reader :issue

    def track_meeting_added_event
      ::Gitlab::Tracking.event('IncidentManagement::ZoomIntegration', 'add_zoom_meeting', label: 'Issue ID', value: issue.id, user: current_user, project: @project, namespace: @project.namespace)
    end

    def track_meeting_removed_event
      ::Gitlab::Tracking.event('IncidentManagement::ZoomIntegration', 'remove_zoom_meeting', label: 'Issue ID', value: issue.id, user: current_user, project: @project, namespace: @project.namespace)
    end

    def add_zoom_meeting(link)
      zoom_meeting = new_zoom_meeting(link)
      response =
        if @issue.persisted?
          # Save the meeting directly since we only want to update one meeting, not all
          zoom_meeting.save
          track_incident_action(current_user, issue, :incident_zoom_meeting)
          success(message: _('Zoom meeting added'))
        else
          success(message: _('Zoom meeting added'), payload: { zoom_meetings: [zoom_meeting] })
        end

      track_meeting_added_event
      SystemNoteService.zoom_link_added(@issue, @project, current_user)

      response
    end

    def new_zoom_meeting(link)
      ZoomMeeting.new(
        issue: @issue,
        project: @project,
        issue_status: :added,
        url: link
      )
    end

    def remove_zoom_meeting
      @added_meeting.update(issue_status: :removed)
      track_meeting_removed_event
      SystemNoteService.zoom_link_removed(@issue, @project, current_user)
    end

    def success(message:, payload: nil)
      ServiceResponse.success(message: message, payload: payload)
    end

    def error(message:)
      ServiceResponse.error(message: message)
    end

    def can_change_link?
      if @issue.persisted?
        can?(current_user, :update_issue, @project)
      else
        can?(current_user, :create_issue, @project)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Issues::ZoomLinkService, feature_category: :team_planning do
  let_it_be(:user) { create(:user) }
  let_it_be(:issue) { create(:issue) }

  let(:project) { issue.project }
  let(:service) { described_class.new(container: project, current_user: user, params: { issue: issue }) }
  let(:zoom_link) { 'https://zoom.us/j/123456789' }

  before do
    project.add_reporter(user)
  end

  shared_context '"added" Zoom meeting' do
    before do
      create(:zoom_meeting, issue: issue)
    end
  end

  shared_context '"removed" zoom meetings' do
    before do
      create(:zoom_meeting, issue: issue, issue_status: :removed)
      create(:zoom_meeting, issue: issue, issue_status: :removed)
    end
  end

  shared_context 'insufficient issue update permissions' do
    before do
      project.add_guest(user)
    end
  end

  shared_context 'insufficient issue create permissions' do
    before do
      expect(service).to receive(:can?).with(user, :create_issue, project).and_return(false)
    end
  end

  describe '#add_link' do
    shared_examples 'can add meeting' do
      it 'appends the new meeting to zoom_meetings' do
        expect(result).to be_success
        expect(ZoomMeeting.canonical_meeting_url(issue)).to eq(zoom_link)
      end

      it 'tracks the add event', :snowplow do
        result

        expect_snowplow_event(
          category: 'IncidentManagement::ZoomIntegration',
          action: 'add_zoom_meeting',
          label: 'Issue ID',
          value: issue.id,
          user: user,
          project: project,
          namespace: project.namespace
        )
      end

      it 'creates a zoom_link_added notification' do
        expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
        expect(SystemNoteService).not_to receive(:zoom_link_removed)
        result
      end
    end

    shared_examples 'cannot add meeting' do
      it 'cannot add the meeting' do
        expect(result).to be_error
        expect(result.message).to eq('Failed to add a Zoom meeting')
      end

      it 'creates no notification' do
        expect(SystemNoteService).not_to receive(:zoom_link_added)
        expect(SystemNoteService).not_to receive(:zoom_link_removed)
        result
      end
    end

    subject(:result) { service.add_link(zoom_link) }

    context 'without existing Zoom meeting' do
      context 'when updating an issue' do
        before do
          allow(issue).to receive(:persisted?).and_return(true)
        end

        include_examples 'can add meeting'

        context 'issue is incident type' do
          let(:issue) { create(:incident) }
          let(:current_user) { user }

          it_behaves_like 'an incident management tracked event', :incident_management_incident_zoom_meeting

          it_behaves_like 'Snowplow event tracking with RedisHLL context' do
            let(:namespace) { issue.namespace }
            let(:category) { described_class.to_s }
            let(:action) { 'incident_management_incident_zoom_meeting' }
            let(:label) { 'redis_hll_counters.incident_management.incident_management_total_unique_counts_monthly' }
          end
        end

        context 'with insufficient issue update permissions' do
          include_context 'insufficient issue update permissions'
          include_examples 'cannot add meeting'
        end
      end

      context 'when creating an issue' do
        before do
          allow(issue).to receive(:persisted?).and_return(false)
        end

        it 'creates a new zoom meeting' do
          expect(result).to be_success
          expect(result.payload[:zoom_meetings][0].url).to eq(zoom_link)
        end

        context 'with insufficient issue create permissions' do
          include_context 'insufficient issue create permissions'
          include_examples 'cannot add meeting'
        end
      end

      context 'with invalid Zoom url' do
        let(:zoom_link) { 'https://not-zoom.link' }

        include_examples 'cannot add meeting'
      end
    end

    context 'with "added" Zoom meeting' do
      include_context '"added" Zoom meeting'
      include_examples 'cannot add meeting'
    end

    context 'with "added" Zoom meeting and race condition' do
      include_context '"added" Zoom meeting'
      before do
        allow(service).to receive(:can_add_link?).and_return(true)
        allow(issue).to receive(:persisted?).and_return(true)
      end

      include_examples 'cannot add meeting'
    end
  end

  describe '#can_add_link?' do
    subject { service.can_add_link? }

    context 'without "added" zoom meeting' do
      it { is_expected.to eq(true) }

      context 'with insufficient issue update permissions' do
        include_context 'insufficient issue update permissions'

        it { is_expected.to eq(false) }
      end
    end

    context 'with Zoom meeting in the issue description' do
      include_context  '"added" Zoom meeting'

      it { is_expected.to eq(false) }
    end
  end

  describe '#remove_link' do
    shared_examples 'cannot remove meeting' do
      it 'cannot remove the meeting' do
        expect(result).to be_error
        expect(result.message).to eq('Failed to remove a Zoom meeting')
      end

      it 'creates no notification' do
        expect(SystemNoteService).not_to receive(:zoom_link_added)
        expect(SystemNoteService).not_to receive(:zoom_link_removed)
        result
      end
    end

    shared_examples 'can remove meeting' do
      it 'creates no notification' do
        expect(SystemNoteService).not_to receive(:zoom_link_added).with(issue, project, user)
        expect(SystemNoteService).to receive(:zoom_link_removed)
        result
      end

      it 'can remove the meeting' do
        expect(result).to be_success
        expect(ZoomMeeting.canonical_meeting_url(issue)).to eq(nil)
      end

      it 'tracks the remove event', :snowplow do
        result

        expect_snowplow_event(
          category: 'IncidentManagement::ZoomIntegration',
          action: 'remove_zoom_meeting',
          label: 'Issue ID',
          value: issue.id,
          user: user,
          project: project,
          namespace: project.namespace
        )
      end
    end

    subject(:result) { service.remove_link }

    context 'with Zoom meeting' do
      include_context '"added" Zoom meeting'

      context 'with existing issue' do
        before do
          allow(issue).to receive(:persisted?).and_return(true)
        end

        include_examples 'can remove meeting'
      end

      context 'without existing issue' do
        before do
          allow(issue).to receive(:persisted?).and_return(false)
        end

        include_examples 'cannot remove meeting'
      end

      context 'with insufficient issue update permissions' do
        include_context 'insufficient issue update permissions'
        include_examples 'cannot remove meeting'
      end
    end

    context 'without "added" Zoom meeting' do
      include_context '"removed" zoom meetings'
      include_examples 'cannot remove meeting'
    end
  end

  describe '#can_remove_link?' do
    subject { service.can_remove_link? }

    context 'without Zoom meeting' do
      it { is_expected.to eq(false) }
    end

    context 'with only "removed" zoom meetings' do
      include_context '"removed" zoom meetings'
      it { is_expected.to eq(false) }
    end

    context 'with "added" Zoom meeting' do
      include_context '"added" Zoom meeting'
      it { is_expected.to eq(true) }

      context 'with "removed" zoom meetings' do
        include_context '"removed" zoom meetings'
        it { is_expected.to eq(true) }
      end

      context 'with insufficient issue update permissions' do
        include_context 'insufficient issue update permissions'
        it { is_expected.to eq(false) }
      end
    end
  end

  describe '#parse_link' do
    subject { service.parse_link(description) }

    context 'with valid Zoom links' do
      where(:description) do
        [
          'Some text https://zoom.us/j/123456789 more text',
          'Mixed https://zoom.us/j/123456789 http://example.com',
          'Multiple link https://zoom.us/my/name https://zoom.us/j/123456789'
        ]
      end

      with_them do
        it { is_expected.to eq('https://zoom.us/j/123456789') }
      end
    end

    context 'with invalid Zoom links' do
      where(:description) do
        [
          nil,
          '',
          'Text only',
          'Non-Zoom http://example.com',
          'Almost Zoom http://zoom.us'
        ]
      end

      with_them do
        it { is_expected.to eq(nil) }
      end
    end
  end
end