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

module Issues
  class UpdateService < Issues::BaseService
    # NOTE: For Issues::UpdateService, we default perform_spam_check to false, because spam_checking is not
    # necessary in many cases, and we don't want to require every caller to explicitly pass it
    # to disable spam checking.
    def initialize(container:, current_user: nil, params: {}, perform_spam_check: false)
      super(container: container, current_user: current_user, params: params)
      @perform_spam_check = perform_spam_check
    end

    def execute(issue)
      handle_move_between_ids(issue)

      change_issue_duplicate(issue)
      move_issue_to_new_project(issue) || clone_issue(issue) || update_task_event(issue) || update(issue)
    end

    def update(issue)
      create_merge_request_from_quick_action

      super
    end

    def before_update(issue, skip_spam_check: false)
      change_work_item_type(issue)

      return if skip_spam_check || !perform_spam_check

      issue.check_for_spam(user: current_user, action: :update)
    end

    def change_work_item_type(issue)
      return unless params[:issue_type].present?

      type_id = find_work_item_type_id(params[:issue_type])

      issue.work_item_type_id = type_id
    end

    def handle_changes(issue, options)
      super
      old_associations = options.fetch(:old_associations, {})
      old_labels = old_associations.fetch(:labels, [])
      old_mentioned_users = old_associations.fetch(:mentioned_users, [])
      old_assignees = old_associations.fetch(:assignees, [])
      old_severity = old_associations[:severity]

      if has_changes?(issue, old_labels: old_labels, old_assignees: old_assignees)
        todo_service.resolve_todos_for_target(issue, current_user)
      end

      if issue.previous_changes.include?('title') ||
          issue.previous_changes.include?('description')
        todo_service.update_issue(issue, current_user, old_mentioned_users)
      end

      handle_assignee_changes(issue, old_assignees)
      handle_confidential_change(issue)
      handle_added_labels(issue, old_labels)
      handle_added_mentions(issue, old_mentioned_users)
      handle_severity_change(issue, old_severity)
      handle_escalation_status_change(issue)
      handle_issue_type_change(issue)
      handle_date_changes(issue)
    end

    def handle_assignee_changes(issue, old_assignees)
      return if issue.assignees == old_assignees

      create_assignee_note(issue, old_assignees)
      Gitlab::ResourceEvents::AssignmentEventRecorder.new(parent: issue, old_assignees: old_assignees).record
      notification_service.async.reassigned_issue(issue, current_user, old_assignees)
      todo_service.reassigned_assignable(issue, current_user, old_assignees)
      track_incident_action(current_user, issue, :incident_assigned)

      GraphqlTriggers.issuable_assignees_updated(issue)
    end

    def handle_task_changes(issuable)
      todo_service.resolve_todos_for_target(issuable, current_user)
      todo_service.update_issue(issuable, current_user)
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def change_issue_duplicate(issue)
      canonical_issue_id = params.delete(:canonical_issue_id)
      return unless canonical_issue_id

      canonical_issue = IssuesFinder.new(current_user).find_by(id: canonical_issue_id)

      if canonical_issue
        Issues::DuplicateService.new(container: project, current_user: current_user).execute(issue, canonical_issue)
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def move_issue_to_new_project(issue)
      target_project = params.delete(:target_project)

      return unless target_project &&
          issue.can_move?(current_user, target_project) &&
          target_project != issue.project

      update(issue)
      Issues::MoveService.new(container: project, current_user: current_user).execute(issue, target_project)
    end

    private

    attr_reader :perform_spam_check

    override :after_update
    def after_update(issue, _old_associations)
      super

      GraphqlTriggers.work_item_updated(issue)
    end

    def handle_date_changes(issue)
      return unless issue.previous_changes.slice('due_date', 'start_date').any?

      GraphqlTriggers.issuable_dates_updated(issue)
    end

    def clone_issue(issue)
      target_project = params.delete(:target_clone_project)
      with_notes = params.delete(:clone_with_notes)

      return unless target_project &&
        issue.can_clone?(current_user, target_project)

      # we've pre-empted this from running in #execute, so let's go ahead and update the Issue now.
      update(issue)
      Issues::CloneService.new(container: project, current_user: current_user).execute(issue, target_project, with_notes: with_notes)
    end

    def create_merge_request_from_quick_action
      create_merge_request_params = params.delete(:create_merge_request)
      return unless create_merge_request_params

      MergeRequests::CreateFromIssueService.new(project: project, current_user: current_user, mr_params: create_merge_request_params).execute
    end

    def handle_confidential_change(issue)
      if issue.previous_changes.include?('confidential')
        # don't enqueue immediately to prevent todos removal in case of a mistake
        TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, issue.id) if issue.confidential?
        create_confidentiality_note(issue)
        track_incident_action(current_user, issue, :incident_change_confidential)
      end
    end

    def handle_added_labels(issue, old_labels)
      added_labels = issue.labels - old_labels

      if added_labels.present?
        notification_service.async.relabeled_issue(issue, added_labels, current_user)
      end
    end

    def handle_added_mentions(issue, old_mentioned_users)
      added_mentions = issue.mentioned_users(current_user) - old_mentioned_users

      if added_mentions.present?
        notification_service.async.new_mentions_in_issue(issue, added_mentions, current_user)
      end
    end

    def handle_severity_change(issue, old_severity)
      return unless old_severity && issue.severity != old_severity

      ::IncidentManagement::AddSeveritySystemNoteWorker.perform_async(issue.id, current_user.id)
    end

    def create_confidentiality_note(issue)
      SystemNoteService.change_issue_confidentiality(issue, issue.project, current_user)
    end

    def handle_issue_type_change(issue)
      return unless issue.previous_changes.include?('work_item_type_id')

      do_handle_issue_type_change(issue)
    end

    def do_handle_issue_type_change(issue)
      old_work_item_type = ::WorkItems::Type.find(issue.work_item_type_id_before_last_save).base_type
      SystemNoteService.change_issue_type(issue, current_user, old_work_item_type)

      ::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute if issue.supports_escalation?
    end

    override :allowed_update_params
    def allowed_update_params(params)
      super.except(:issue_type)
    end
  end
end

Issues::UpdateService.prepend_mod_with('Issues::UpdateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Issues::UpdateService, :mailer, feature_category: :team_planning do
  let_it_be(:user) { create(:user) }
  let_it_be(:user2) { create(:user) }
  let_it_be(:user3) { create(:user) }
  let_it_be(:guest) { create(:user) }
  let_it_be(:group) { create(:group, :public, :crm_enabled) }
  let_it_be(:project, reload: true) { create(:project, :repository, group: group) }
  let_it_be(:label) { create(:label, title: 'a', project: project) }
  let_it_be(:label2) { create(:label, title: 'b', project: project) }
  let_it_be(:label3) { create(:label, title: 'c', project: project) }
  let_it_be(:milestone) { create(:milestone, project: project) }

  let(:container) { project }
  let(:issue) do
    create(
      :issue,
      title: 'Old title',
      description: "for #{user2.to_reference}",
      assignee_ids: [user3.id],
      project: project,
      author: create(:user)
    )
  end

  before_all do
    group.add_maintainer(user)
    group.add_developer(user2)
    group.add_developer(user3)
    group.add_guest(guest)
  end

  describe 'execute' do
    let_it_be(:contact) { create(:contact, group: group) }

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

    def find_notes(action)
      issue
        .notes
        .joins(:system_note_metadata)
        .where(system_note_metadata: { action: action })
    end

    def update_issue(opts)
      described_class.new(container: container, current_user: user, params: opts).execute(issue)
    end

    it_behaves_like 'issuable update service updating last_edited_at values' do
      let(:issuable) { issue }
      subject(:update_issuable) { update_issue(update_params) }
    end

    context 'valid params' do
      let(:opts) do
        {
          title: 'New title',
          description: 'Also please fix',
          assignee_ids: [user2.id],
          state_event: 'close',
          label_ids: [label&.id],
          due_date: Date.tomorrow,
          discussion_locked: true,
          severity: 'low',
          milestone_id: milestone.id,
          add_contacts: [contact.email]
        }
      end

      context 'when an unauthorized project_id is provided' do
        let(:unauthorized_project) { create(:project) }

        before do
          opts[:project_id] = unauthorized_project.id
        end

        it 'ignores the project_id param and does not update the issue\'s project' do
          expect do
            update_issue(opts)
            unauthorized_project.reload
          end.to not_change { unauthorized_project.issues.count }

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

      it 'updates the issue with the given params' do
        expect(TodosDestroyer::ConfidentialIssueWorker).not_to receive(:perform_in)

        update_issue(opts)

        expect(issue).to be_valid
        expect(issue.title).to eq 'New title'
        expect(issue.description).to eq 'Also please fix'
        expect(issue.assignees).to match_array([user2])
        expect(issue).to be_closed
        expect(issue.labels).to match_array [label]
        expect(issue.due_date).to eq Date.tomorrow
        expect(issue.discussion_locked).to be_truthy
        expect(issue.confidential).to be_falsey
        expect(issue.milestone).to eq milestone
        expect(issue.issue_customer_relations_contacts.last.contact).to eq contact
      end

      it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
        subject(:execute_service) { update_issue(opts) }
      end

      context 'when updating milestone' do
        before do
          update_issue({ milestone_id: nil })
        end

        it 'updates issue milestone when passing `milestone` param' do
          expect { update_issue({ milestone_id: milestone.id }) }
            .to change(issue, :milestone).to(milestone).from(nil)
        end

        it "triggers 'issuableMilestoneUpdated'" do
          expect(GraphqlTriggers).to receive(:issuable_milestone_updated).with(issue).and_call_original

          update_issue({ milestone_id: milestone.id })
        end

        context 'when milestone remains unchanged' do
          before do
            update_issue({ title: 'abc', milestone_id: milestone.id })
          end

          it "does not trigger 'issuableMilestoneUpdated'" do
            expect(GraphqlTriggers).not_to receive(:issuable_milestone_updated)

            update_issue({ milestone_id: milestone.id })
          end
        end
      end

      context 'when sentry identifier is given' do
        before do
          sentry_attributes = { sentry_issue_attributes: { sentry_issue_identifier: 42 } }
          opts.merge!(sentry_attributes)
        end

        it 'assigns the sentry error' do
          update_issue(opts)

          expect(issue.sentry_issue).to be_kind_of(SentryIssue)
        end

        context 'user is a guest' do
          let(:user) { guest }

          it 'does not assign the sentry error' do
            update_issue(opts)

            expect(issue.sentry_issue).to eq(nil)
          end
        end
      end

      context 'when issue type is not incident' do
        before do
          update_issue(opts)
        end

        it_behaves_like 'not an incident issue'

        context 'when confidentiality is changed' do
          subject { update_issue(confidential: true) }

          it_behaves_like 'does not track incident management event'
        end
      end

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

        before do
          update_issue(opts)
        end

        it_behaves_like 'incident issue'

        it 'does not add an incident label' do
          expect(issue.labels).to match_array [label]
        end

        context 'when confidentiality is changed' do
          let(:current_user) { user }

          subject { update_issue(confidential: true) }

          it_behaves_like 'an incident management tracked event', :incident_management_incident_change_confidential

          it_behaves_like 'Snowplow event tracking with RedisHLL context' do
            let(:namespace) { issue.namespace }
            let(:category) { described_class.to_s }
            let(:label) { 'redis_hll_counters.incident_management.incident_management_total_unique_counts_monthly' }
            let(:action) { 'incident_management_incident_change_confidential' }
            let(:opts) do
              {
                title: 'New title',
                description: 'Also please fix',
                assignee_ids: [user2.id],
                state_event: 'close',
                due_date: Date.tomorrow,
                discussion_locked: true,
                severity: 'low',
                milestone_id: milestone.id,
                add_contacts: [contact.email]
              }
            end
          end
        end
      end

      it 'refreshes the number of open issues when the issue is made confidential', :use_clean_rails_memory_store_caching do
        issue # make sure the issue is created first so our counts are correct.

        expect do
          update_issue(confidential: true)

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

      it 'enqueues ConfidentialIssueWorker when an issue is made confidential' do
        expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, issue.id)

        update_issue(confidential: true)

        expect(issue.confidential).to be_truthy
      end

      it 'does not enqueue ConfidentialIssueWorker when an issue is made non confidential' do
        # set confidentiality to true before the actual update
        issue.update!(confidential: true)

        expect(TodosDestroyer::ConfidentialIssueWorker).not_to receive(:perform_in)

        update_issue(confidential: false)

        expect(issue.confidential).to be_falsey
      end

      context 'changing issue_type' do
        let!(:label_1) { create(:label, project: project, title: 'incident') }
        let!(:label_2) { create(:label, project: project, title: 'missed-sla') }

        before do
          stub_licensed_features(quality_management: true)
        end

        context 'from issue to incident' do
          it_behaves_like 'incident issue' do
            before do
              update_issue(**opts, issue_type: 'incident')
            end
          end

          it 'creates system note about issue type' do
            update_issue(issue_type: 'incident')

            note = find_note('changed type from issue to incident')

            expect(note).not_to eq(nil)
          end

          it 'creates an escalation status' do
            expect { update_issue(issue_type: 'incident') }
            .to change { issue.reload.incident_management_issuable_escalation_status }
            .from(nil)
            .to(a_kind_of(IncidentManagement::IssuableEscalationStatus))
          end

          context 'for an issue with multiple labels' do
            let(:issue) { create(:incident, project: project, labels: [label_1]) }

            before do
              update_issue(issue_type: 'incident')
            end

            it 'does not add an `incident` label if one already exist' do
              expect(issue.labels).to eq([label_1])
            end
          end
        end

        context 'from incident to issue' do
          let(:issue) { create(:incident, project: project) }

          it 'changed from an incident to an issue type' do
            expect { update_issue(issue_type: 'issue') }
              .to change(issue, :issue_type).from('incident').to('issue')
              .and(change { issue.work_item_type.base_type }.from('incident').to('issue'))
          end

          context 'for an incident with multiple labels' do
            let(:issue) { create(:incident, project: project, labels: [label_1, label_2]) }

            it 'does not remove an `incident` label if one exists on the incident' do
              expect { update_issue(issue_type: 'issue') }.to not_change(issue, :label_ids)
            end
          end

          context 'filtering the incident label' do
            let(:issue) { create(:incident, project: project, labels: [label_1, label_2]) }
            let(:params) { { label_ids: [label_1.id, label_2.id], remove_label_ids: [] } }

            it 'does not add an incident label id to remove_label_ids for it to be removed' do
              expect { update_issue(issue_type: 'issue') }.to not_change(issue, :label_ids)
            end
          end
        end

        context 'from issue to restricted issue types' do
          context 'without sufficient permissions' do
            let(:user) { guest }

            it 'does nothing to the labels' do
              expect { update_issue(issue_type: 'issue') }.not_to change(issue.labels, :count)
              expect(issue.reload.labels).to eq([])
            end
          end
        end
      end

      it 'updates open issue counter for assignees when issue is reassigned' do
        update_issue(assignee_ids: [user2.id])

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

      context 'when changing relative position' do
        let(:issue1) { create(:issue, project: project, assignees: [user3]) }
        let(:issue2) { create(:issue, project: project, assignees: [user3]) }

        before do
          [issue, issue1, issue2].each do |issue|
            issue.move_to_end
            issue.save!
          end
        end

        it 'sorts issues as specified by parameters' do
          opts[:move_between_ids] = [issue1.id, issue2.id]

          update_issue(opts)

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

        context 'when block_issue_positioning flag is enabled' do
          before do
            stub_feature_flags(block_issue_repositioning: true)
          end

          it 'raises error' do
            old_position = issue.relative_position
            opts[:move_between_ids] = [issue1.id, issue2.id]

            expect { update_issue(opts) }.to raise_error(::Gitlab::RelativePositioning::IssuePositioningDisabled)
            expect(issue.reload.relative_position).to eq(old_position)
          end
        end
      end

      it 'rebalances if needed on the left' do
        range = described_class::NO_REBALANCING_NEEDED
        issue1 = create(:issue, project: project, relative_position: range.first - 100)
        issue2 = create(:issue, project: project, relative_position: range.first)
        issue.update!(relative_position: RelativePositioning::START_POSITION)

        opts[:move_between_ids] = [issue1.id, issue2.id]

        expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)

        update_issue(opts)
        expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
      end

      it 'rebalances if needed on the right' do
        range = described_class::NO_REBALANCING_NEEDED
        issue1 = create(:issue, project: project, relative_position: range.last)
        issue2 = create(:issue, project: project, relative_position: range.last + 100)
        issue.update!(relative_position: RelativePositioning::START_POSITION)

        opts[:move_between_ids] = [issue1.id, issue2.id]

        expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)

        update_issue(opts)
        expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
      end

      context 'when moving issue between issues from different projects' do
        let(:group) { create(:group) }
        let(:subgroup) { create(:group, parent: group) }

        let(:project_1) { create(:project, namespace: group) }
        let(:project_2) { create(:project, namespace: group) }
        let(:project_3) { create(:project, namespace: subgroup) }

        let(:issue_1) { create(:issue, project: project_1) }
        let(:issue_2) { create(:issue, project: project_2) }
        let(:issue_3) { create(:issue, project: project_3) }

        before do
          group.add_developer(user)
        end

        it 'sorts issues as specified by parameters' do
          # Moving all issues to end here like the last example won't work since
          # all projects only have the same issue count
          # so their relative_position will be the same.
          issue_1.move_to_end
          issue_2.move_after(issue_1)
          issue_3.move_after(issue_2)
          [issue_1, issue_2, issue_3].map(&:save)

          opts[:move_between_ids] = [issue_1.id, issue_2.id]

          described_class.new(container: issue_3.project, current_user: user, params: opts).execute(issue_3)
          expect(issue_2.relative_position).to be_between(issue_1.relative_position, issue_2.relative_position)
        end
      end

      context 'when current user cannot admin issues in the project' do
        it 'filters out params that cannot be set without the :admin_issue permission' do
          described_class.new(
            container: project, current_user: guest, params: opts.merge(
              confidential: true,
              issue_type: 'test_case'
            )
          ).execute(issue)

          expect(issue).to be_valid
          expect(issue.title).to eq 'New title'
          expect(issue.description).to eq 'Also please fix'
          expect(issue.assignees).to match_array [user3]
          expect(issue.labels).to be_empty
          expect(issue.milestone).to be_nil
          expect(issue.due_date).to be_nil
          expect(issue.discussion_locked).to be_falsey
          expect(issue.confidential).to be_falsey
          expect(issue.issue_type).to eql('issue')
        end
      end

      context 'with background jobs processed', :sidekiq_might_not_need_inline do
        before do
          perform_enqueued_jobs do
            update_issue(opts)
          end
        end

        it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
          deliveries = ActionMailer::Base.deliveries
          email = deliveries.last
          recipients = deliveries.last(2).flat_map(&:to)
          expect(recipients).to include(user2.email, user3.email)
          expect(email.subject).to include(issue.title)
        end

        it 'creates system note about issue reassign' do
          note = find_note('assigned to')

          expect(note.note).to include "assigned to #{user2.to_reference}"
        end

        it 'creates a resource label event' do
          event = issue.resource_label_events.last

          expect(event).not_to be_nil
          expect(event.label_id).to eq label.id
          expect(event.user_id).to eq user.id
        end

        it 'creates system note about title change' do
          note = find_note('changed title')

          expect(note.note).to eq 'changed title from **{-Old-} title** to **{+New+} title**'
        end

        it 'creates system note about discussion lock' do
          note = find_note('locked the discussion in this issue')

          expect(note.note).to eq 'locked the discussion in this issue'
        end
      end

      context 'after_save callback to store_mentions' do
        let(:issue) { create(:issue, title: 'Old title', description: "simple description", project: project, author: create(:user)) }
        let(:labels) { create_pair(:label, project: project) }
        let(:milestone) { create(:milestone, project: project) }

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

          it 'saves mentions' do
            expect(issue).to receive(:store_mentions!).and_call_original

            expect { update_issue(opts) }.to change { IssueUserMention.count }.by(1)

            expect(issue.referenced_users).to match_array([user])
          end
        end

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

          it 'does not call store_mentions' do
            expect(issue).not_to receive(:store_mentions!).and_call_original

            expect { update_issue(opts) }.not_to change { IssueUserMention.count }

            expect(issue.referenced_users).to be_empty
          end
        end

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

          it 'does not call store_mentions' do
            expect(issue).not_to receive(:store_mentions!).and_call_original

            expect { update_issue(opts) }.not_to change { IssueUserMention.count }

            expect(issue.referenced_users).to be_empty
            expect(issue.valid?).to be false
          end
        end
      end
    end

    context 'when description changed' do
      it 'creates system note about description change' do
        update_issue(description: 'Changed description')

        note = find_note('changed the description')

        expect(note.note).to eq('changed the description')
      end

      it 'triggers GraphQL description updated subscription' do
        expect(GraphqlTriggers).to receive(:issuable_description_updated).with(issue).and_call_original

        update_issue(description: 'Changed description')
      end
    end

    context 'when description is not changed' do
      it 'does not trigger GraphQL description updated subscription' do
        expect(GraphqlTriggers).not_to receive(:issuable_description_updated)

        update_issue(title: 'Changed title')
      end
    end

    context 'when issue turns confidential' do
      let(:opts) do
        {
          title: 'New title',
          description: 'Also please fix',
          assignee_ids: [user2],
          state_event: 'close',
          label_ids: [label.id],
          confidential: true
        }
      end

      it 'creates system note about confidentiality change' do
        update_issue(confidential: true)

        note = find_note('made the issue confidential')

        expect(note.note).to eq 'made the issue confidential'
      end

      it 'executes confidential issue hooks' do
        expect(project.project_namespace).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
        expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :confidential_issue_hooks)

        update_issue(confidential: true)
      end

      it 'does not update assignee_id with unauthorized users' do
        project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
        update_issue(confidential: true)
        non_member = create(:user)
        original_assignees = issue.assignees

        update_issue(assignee_ids: [non_member.id])

        expect(issue.reload.assignees).to eq(original_assignees)
      end
    end

    context 'todos' do
      let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }

      context 'when the title change' do
        before do
          update_issue(title: 'New title')
        end

        it 'marks pending todos as done' do
          expect(todo.reload.done?).to eq true
        end

        it 'does not create any new todos' do
          expect(Todo.count).to eq(1)
        end
      end

      context 'when the description change' do
        before do
          update_issue(description: "Also please fix #{user2.to_reference} #{user3.to_reference}")
        end

        it 'marks todos as done' do
          expect(todo.reload.done?).to eq true
        end

        it 'creates only 1 new todo' do
          expect(Todo.count).to eq(2)
        end
      end

      context 'when is reassigned' do
        before do
          update_issue(assignees: [user2])
        end

        it 'marks previous assignee todos as done' do
          expect(todo.reload.done?).to eq true
        end

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

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

      context 'when a new assignee added' do
        subject { update_issue(assignees: issue.assignees + [user2]) }

        it 'creates only 1 new todo' do
          expect { subject }.to change { Todo.count }.by(1)
        end

        it 'creates a todo for new assignee' do
          subject

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

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

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

          it_behaves_like 'an incident management tracked event', :incident_management_incident_assigned

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

      context 'when the milestone is removed' do
        let!(:non_subscriber) { create(:user) }

        let!(:subscriber) do
          create(:user) do |u|
            issue.toggle_subscription(u, project)
            project.add_developer(u)
          end
        end

        it 'sends notifications for subscribers of changed milestone', :sidekiq_might_not_need_inline do
          issue.milestone = create(:milestone, project: project)

          issue.save!

          perform_enqueued_jobs do
            update_issue(milestone_id: "")
          end

          should_email(subscriber)
          should_not_email(non_subscriber)
        end

        it 'clears milestone issue counters cache' do
          issue.milestone = create(:milestone, project: project)

          issue.save!

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

          update_issue(milestone_id: "")
        end
      end

      context 'when the milestone is assigned' do
        let!(:non_subscriber) { create(:user) }

        let!(:subscriber) do
          create(:user) do |u|
            issue.toggle_subscription(u, project)
            project.add_developer(u)
          end
        end

        it 'marks todos as done' do
          update_issue(milestone_id: create(:milestone, project: project).id)

          expect(todo.reload.done?).to eq true
        end

        it 'sends notifications for subscribers of changed milestone', :sidekiq_might_not_need_inline do
          perform_enqueued_jobs do
            update_issue(milestone_id: create(:milestone, project: project).id)
          end

          should_email(subscriber)
          should_not_email(non_subscriber)
        end

        it 'deletes issue counters cache for the milestone' do
          milestone = create(:milestone, project: project)

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

          update_issue(milestone_id: milestone.id)
        end
      end

      context 'when the milestone is changed' do
        it 'deletes issue counters cache for both milestones' do
          old_milestone = create(:milestone, project: project)
          new_milestone = create(:milestone, project: project)

          issue.update!(milestone: old_milestone)

          expect_next_instance_of(Milestones::IssuesCountService, old_milestone) do |service|
            expect(service).to receive(:delete_cache).and_call_original
          end
          expect_next_instance_of(Milestones::ClosedIssuesCountService, old_milestone) do |service|
            expect(service).to receive(:delete_cache).and_call_original
          end
          expect_next_instance_of(Milestones::IssuesCountService, new_milestone) do |service|
            expect(service).to receive(:delete_cache).and_call_original
          end
          expect_next_instance_of(Milestones::ClosedIssuesCountService, new_milestone) do |service|
            expect(service).to receive(:delete_cache).and_call_original
          end

          update_issue(milestone_id: new_milestone.id)
        end
      end

      context 'when the labels change' do
        before do
          travel_to(1.minute.from_now) do
            update_issue(label_ids: [label.id])
          end
        end

        it 'marks todos as done' do
          expect(todo.reload.done?).to eq true
        end

        it 'updates updated_at' do
          expect(issue.reload.updated_at).to be_future
        end
      end
    end

    context 'when the issue is relabeled' do
      let!(:non_subscriber) { create(:user) }

      let!(:subscriber) do
        create(:user) do |u|
          label.toggle_subscription(u, project)
          project.add_developer(u)
        end
      end

      it 'sends notifications for subscribers of newly added labels', :sidekiq_might_not_need_inline do
        opts = { label_ids: [label.id] }

        perform_enqueued_jobs do
          @issue = described_class.new(container: project, current_user: user, params: opts).execute(issue)
        end

        should_email(subscriber)
        should_not_email(non_subscriber)
      end

      context 'when issue has the `label` label' do
        before do
          issue.labels << label
        end

        it 'does not send notifications for existing labels' do
          opts = { label_ids: [label.id, label2.id] }

          perform_enqueued_jobs do
            @issue = described_class.new(container: project, current_user: user, params: opts).execute(issue)
          end

          should_not_email(subscriber)
          should_not_email(non_subscriber)
        end

        it 'does not send notifications for removed labels' do
          opts = { label_ids: [label2.id] }

          perform_enqueued_jobs do
            @issue = described_class.new(container: project, current_user: user, params: opts).execute(issue)
          end

          should_not_email(subscriber)
          should_not_email(non_subscriber)
        end
      end
    end

    context 'when issue has tasks' do
      before do
        update_issue(description: "- [ ] Task 1\n- [ ] Task 2")
      end

      it { expect(issue.tasks?).to eq(true) }

      it_behaves_like 'updating a single task'

      context 'when tasks are marked as completed' do
        before do
          update_issue(description: "- [x] Task 1\n- [X] Task 2")
        end

        it 'does not check for spam on task status change' do
          params = {
            update_task: {
              index: 1,
              checked: false,
              line_source: '- [x] Task 1',
              line_number: 1
            }
          }
          service = described_class.new(container: project, current_user: user, params: params)

          expect(issue).not_to receive(:check_for_spam)

          service.execute(issue)
        end

        # At the moment of writting old associations are not necessary for update_task
        # and doing this will prevent fetching associations from the DB and comparing old and new labels
        it 'does not pass old_associations to the after_update method' do
          params = {
            update_task: {
              index: 1,
              checked: false,
              line_source: '- [x] Task 1',
              line_number: 1
            }
          }
          service = described_class.new(container: project, current_user: user, params: params)

          expect(service).to receive(:after_update).with(issue, {})

          service.execute(issue)
        end

        it 'creates system note about task status change' do
          note1 = find_note('marked the checklist item **Task 1** as completed')
          note2 = find_note('marked the checklist item **Task 2** as completed')

          expect(note1).not_to be_nil
          expect(note2).not_to be_nil

          description_notes = find_notes('description')
          expect(description_notes.length).to eq(1)
        end
      end

      context 'when tasks are marked as incomplete' do
        before do
          update_issue(description: "- [x] Task 1\n- [X] Task 2")
          update_issue(description: "- [ ] Task 1\n- [ ] Task 2")
        end

        it 'creates system note about task status change' do
          note1 = find_note('marked the checklist item **Task 1** as incomplete')
          note2 = find_note('marked the checklist item **Task 2** as incomplete')

          expect(note1).not_to be_nil
          expect(note2).not_to be_nil

          description_notes = find_notes('description')
          expect(description_notes.length).to eq(1)
        end
      end

      context 'when tasks position has been modified' do
        before do
          update_issue(description: "- [x] Task 1\n- [X] Task 2")
          update_issue(description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2")
        end

        it 'does not create a system note for the task' do
          task_note = find_note('marked the checklist item **Task 2** as incomplete')
          description_notes = find_notes('description')

          expect(task_note).to be_nil
          expect(description_notes.length).to eq(2)
        end
      end

      context 'when a Task list with a completed item is totally replaced' do
        before do
          update_issue(description: "- [ ] Task 1\n- [X] Task 2")
          update_issue(description: "- [ ] One\n- [ ] Two\n- [ ] Three")
        end

        it 'does not create a system note referencing the position the old item' do
          task_note = find_note('marked the checklist item **Two** as incomplete')
          description_notes = find_notes('description')

          expect(task_note).to be_nil
          expect(description_notes.length).to eq(2)
        end

        it 'does not generate a new note at all' do
          expect do
            update_issue(description: "- [ ] One\n- [ ] Two\n- [ ] Three")
          end.not_to change { Note.count }
        end
      end
    end

    context 'updating labels' do
      let(:label_a) { label }
      let(:label_b) { label2 }
      let(:label_c) { label3 }
      let(:label_locked) { create(:label, title: 'locked', project: project, lock_on_merge: true) }
      let(:issuable) { issue }

      it_behaves_like 'updating issuable labels'
      it_behaves_like 'keeps issuable labels sorted after update'
      it_behaves_like 'broadcasting issuable labels updates'

      context 'when the issue belongs directly to a group' do
        let(:container) { group }

        it_behaves_like 'updating issuable labels'
      end

      def update_issuable(update_params)
        update_issue(update_params)
      end
    end

    context 'updating dates' do
      subject(:result) { described_class.new(container: project, current_user: user, params: params).execute(issue) }

      let(:updated_date) { 1.week.from_now.to_date }

      shared_examples 'issue update service that triggers date updates' do
        it 'triggers graphql date updated subscription' do
          expect(GraphqlTriggers).to receive(:issuable_dates_updated).with(issue).and_call_original

          result
        end
      end

      shared_examples 'issue update service that does not trigger date updates' do
        it 'does not trigger date updated subscriptions' do
          expect(GraphqlTriggers).not_to receive(:issuable_dates_updated)

          result
        end
      end

      context 'when due_date is updated' do
        let(:params) { { due_date: updated_date } }

        it_behaves_like 'issue update service that triggers date updates'
      end

      context 'when start_date is updated' do
        let(:params) { { start_date: updated_date } }

        it_behaves_like 'issue update service that triggers date updates'
      end

      context 'when no date is updated' do
        let(:params) { { title: 'should not trigger date updates' } }

        it_behaves_like 'issue update service that does not trigger date updates'
      end

      context 'when update is not successful but date is provided' do
        let(:params) { { title: '', due_date: updated_date } }

        it_behaves_like 'issue update service that does not trigger date updates'
      end
    end

    context 'updating asssignee_id' do
      it 'changes assignee' do
        expect_next_instance_of(NotificationService::Async) do |service|
          expect(service).to receive(:reassigned_issue).with(issue, user, [user3])
        end

        update_issue(assignee_ids: [user2.id])

        expect(issue.reload.assignees).to eq([user2])
      end

      it 'does not update assignee when assignee_id is invalid' do
        expect(NotificationService).not_to receive(:new)

        update_issue(assignee_ids: [-1])

        expect(issue.reload.assignees).to eq([user3])
      end

      it 'unassigns assignee when user id is 0' do
        expect_next_instance_of(NotificationService::Async) do |service|
          expect(service).to receive(:reassigned_issue).with(issue, user, [user3])
        end

        update_issue(assignee_ids: [0])

        expect(issue.reload.assignees).to be_empty
      end

      it 'does not update assignee_id when user cannot read issue' do
        expect(NotificationService).not_to receive(:new)

        update_issue(assignee_ids: [create(:user).id])

        expect(issue.reload.assignees).to eq([user3])
      end

      context "when issuable feature is private" do
        levels = [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC]

        levels.each do |level|
          it "does not update with unauthorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do
            expect(NotificationService).not_to receive(:new)

            assignee = create(:user)
            project.update!(visibility_level: level)
            feature_visibility_attr = :"#{issue.model_name.plural}_access_level"
            project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE)

            expect { update_issue(assignee_ids: [assignee.id]) }.not_to change { issue.assignees }
          end
        end
      end

      it 'tracks the assignment events' do
        original_assignee = issue.assignees.first!

        update_issue(assignee_ids: [user2.id])
        update_issue(assignee_ids: [])
        update_issue(assignee_ids: [user3.id])

        expected_events = [
          have_attributes({
            issue_id: issue.id,
            user_id: original_assignee.id,
            action: 'remove'
          }),
          have_attributes({
            issue_id: issue.id,
            user_id: user2.id,
            action: 'add'
          }),
          have_attributes({
            issue_id: issue.id,
            user_id: user2.id,
            action: 'remove'
          }),
          have_attributes({
            issue_id: issue.id,
            user_id: user3.id,
            action: 'add'
          })
        ]

        expect(issue.assignment_events).to match_array(expected_events)
      end
    end

    context 'updating mentions' do
      let(:mentionable) { issue }

      include_examples 'updating mentions', described_class
    end

    context 'updating severity' do
      let(:opts) { { severity: 'low' } }

      shared_examples 'updates the severity' do |expected_severity|
        it 'has correct value' do
          update_issue(opts)

          expect(issue.severity).to eq(expected_severity)
        end

        it 'creates a system note' do
          expect(::IncidentManagement::AddSeveritySystemNoteWorker).to receive(:perform_async).with(issue.id, user.id)

          update_issue(opts)
        end

        it 'triggers webhooks' do
          expect(project.project_namespace).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
          expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks)
          expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :incident_hooks)

          update_issue(opts)
        end
      end

      shared_examples 'does not change the severity' do
        it 'retains the original value' do
          expected_severity = issue.severity

          update_issue(opts)

          expect(issue.severity).to eq(expected_severity)
        end

        it 'does not trigger side-effects' do
          expect(::IncidentManagement::AddSeveritySystemNoteWorker).not_to receive(:perform_async)
          expect(project).not_to receive(:execute_hooks)
          expect(project).not_to receive(:execute_integrations)

          expect { update_issue(opts) }.not_to change(IssuableSeverity, :count)
        end
      end

      context 'on incidents' do
        let(:issue) { create(:incident, project: project) }

        context 'when severity has not been set previously' do
          it_behaves_like 'updates the severity', 'low'

          it 'creates a new record' do
            expect { update_issue(opts) }.to change(IssuableSeverity, :count).by(1)
          end

          context 'with unsupported severity value' do
            let(:opts) { { severity: 'unsupported-severity' } }

            it_behaves_like 'does not change the severity'
          end

          context 'with severity value defined but unchanged' do
            let(:opts) { { severity: IssuableSeverity::DEFAULT } }

            it_behaves_like 'does not change the severity'
          end

          context 'as guest' do
            let(:user) { guest }

            it_behaves_like 'does not change the severity'

            context 'and also author' do
              let(:issue) { create(:incident, project: project, author: user) }

              it_behaves_like 'does not change the severity'
            end

            context 'and also assignee' do
              let(:issue) { create(:incident, project: project, assignee_ids: [user.id]) }

              it_behaves_like 'does not change the severity'
            end
          end
        end

        context 'when severity has been set before' do
          before do
            create(:issuable_severity, issue: issue, severity: 'high')
          end

          it_behaves_like 'updates the severity', 'low'

          it 'does not create a new record' do
            expect { update_issue(opts) }.not_to change(IssuableSeverity, :count)
          end

          context 'with unsupported severity value' do
            let(:opts) { { severity: 'unsupported-severity' } }

            it_behaves_like 'updates the severity', IssuableSeverity::DEFAULT
          end

          context 'with severity value defined but unchanged' do
            let(:opts) { { severity: issue.severity } }

            it_behaves_like 'does not change the severity'
          end
        end
      end

      context 'when issue type is not incident' do
        it_behaves_like 'does not change the severity'
      end
    end

    context 'updating escalation status' do
      let(:opts) { { escalation_status: { status: 'acknowledged' } } }
      let(:escalation_update_class) { ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService }

      shared_examples 'updates the escalation status record' do |expected_status|
        let(:service_double) { instance_double(escalation_update_class) }

        it 'has correct value' do
          expect(escalation_update_class).to receive(:new).with(issue, user).and_return(service_double)
          expect(service_double).to receive(:execute)

          update_issue(opts)

          expect(issue.escalation_status.status_name).to eq(expected_status)
        end

        it 'triggers webhooks' do
          expect(project.project_namespace).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
          expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks)
          expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :incident_hooks)

          update_issue(opts)
        end
      end

      shared_examples 'does not change the status record' do
        it 'retains the original value' do
          expected_status = issue.escalation_status&.status_name

          update_issue(opts)

          expect(issue.escalation_status&.status_name).to eq(expected_status)
        end

        it 'does not trigger side-effects' do
          expect(project).not_to receive(:execute_hooks)
          expect(project).not_to receive(:execute_integrations)

          update_issue(opts)
        end
      end

      context 'when issue is an incident' do
        let(:issue) { create(:incident, project: project) }

        context 'with an escalation status record' do
          before do
            create(:incident_management_issuable_escalation_status, issue: issue)
          end

          it_behaves_like 'updates the escalation status record', :acknowledged

          context 'with unsupported status value' do
            let(:opts) { { escalation_status: { status: 'unsupported-status' } } }

            it_behaves_like 'does not change the status record'
          end

          context 'with status value defined but unchanged' do
            let(:opts) { { escalation_status: { status: issue.escalation_status.status_name } } }

            it_behaves_like 'does not change the status record'
          end
        end

        context 'without an escalation status record' do
          it 'creates a new record' do
            expect { update_issue(opts) }.to change(::IncidentManagement::IssuableEscalationStatus, :count).by(1)
          end

          it_behaves_like 'updates the escalation status record', :acknowledged
        end
      end

      context 'when issue type is not incident' do
        it_behaves_like 'does not change the status record'
      end
    end

    context 'duplicate issue' do
      let(:canonical_issue) { create(:issue, project: project) }

      context 'invalid canonical_issue_id' do
        it 'does not call the duplicate service' do
          expect(Issues::DuplicateService).not_to receive(:new)

          update_issue(canonical_issue_id: 123456789)
        end
      end

      context 'valid canonical_issue_id' do
        it 'calls the duplicate service with both issues' do
          expect_next_instance_of(Issues::DuplicateService) do |service|
            expect(service).to receive(:execute).with(issue, canonical_issue)
          end

          update_issue(canonical_issue_id: canonical_issue.id)
        end
      end
    end

    context 'move issue to another project' do
      let(:target_project) { create(:project) }

      context 'valid project' do
        before do
          target_project.add_maintainer(user)
        end

        it 'calls the move service with the proper issue and project' do
          move_stub = instance_double(Issues::MoveService)
          allow(Issues::MoveService).to receive(:new).and_return(move_stub)
          allow(move_stub).to receive(:execute).with(issue, target_project).and_return(issue)

          expect(move_stub).to receive(:execute).with(issue, target_project)

          update_issue(target_project: target_project)
        end
      end
    end

    context 'clone an issue' do
      context 'valid project' do
        let(:target_project) { create(:project) }

        before do
          target_project.add_maintainer(user)
        end

        it 'calls the move service with the proper issue and project' do
          clone_stub = instance_double(Issues::CloneService)
          allow(Issues::CloneService).to receive(:new).and_return(clone_stub)
          allow(clone_stub).to receive(:execute).with(issue, target_project, with_notes: nil).and_return(issue)

          expect(clone_stub).to receive(:execute).with(issue, target_project, with_notes: nil)

          update_issue(target_clone_project: target_project)
        end
      end
    end

    context 'clone an issue with notes' do
      context 'valid project' do
        let(:target_project) { create(:project) }

        before do
          target_project.add_maintainer(user)
        end

        it 'calls the move service with the proper issue and project' do
          clone_stub = instance_double(Issues::CloneService)
          allow(Issues::CloneService).to receive(:new).and_return(clone_stub)
          allow(clone_stub).to receive(:execute).with(issue, target_project, with_notes: true).and_return(issue)

          expect(clone_stub).to receive(:execute).with(issue, target_project, with_notes: true)

          update_issue(target_clone_project: target_project, clone_with_notes: true)
        end
      end
    end

    context 'when moving an issue ' do
      it 'raises an error for invalid move ids' do
        opts = { move_between_ids: [9000, non_existing_record_id] }

        expect { described_class.new(container: issue.project, current_user: user, params: opts).execute(issue) }
            .to raise_error(ActiveRecord::RecordNotFound)
      end
    end

    it_behaves_like 'issuable update service' do
      let(:open_issuable) { issue }
      let(:closed_issuable) { create(:closed_issue, project: project) }
    end

    context 'broadcasting issue assignee updates' do
      let(:update_params) { { assignee_ids: [user2.id] } }

      it 'triggers the GraphQL subscription' do
        expect(GraphqlTriggers).to receive(:issuable_assignees_updated).with(issue)

        update_issue(update_params)
      end

      context 'when assignee is not updated' do
        let(:update_params) { { title: 'Some other title' } }

        it 'does not trigger the GraphQL subscription' do
          expect(GraphqlTriggers).not_to receive(:issuable_assignees_updated).with(issue)

          update_issue(update_params)
        end
      end
    end

    it_behaves_like 'issuable record that supports quick actions' do
      let(:existing_issue) { create(:issue, project: project) }
      let(:issuable) { described_class.new(container: project, current_user: user, params: params).execute(existing_issue) }
    end
  end
end


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

module Issues
  class CreateService < Issues::BaseService
    include ResolveDiscussions
    prepend RateLimitedService
    include ::Services::ReturnServiceResponses

    rate_limit key: :issues_create,
      opts: { scope: [:project, :current_user, :external_author] }

    def initialize(container:, current_user: nil, params: {}, build_service: nil, perform_spam_check: true)
      @extra_params = params.delete(:extra_params) || {}
      super(container: container, current_user: current_user, params: params)
      @perform_spam_check = perform_spam_check
      @build_service = build_service ||
        BuildService.new(container: project, current_user: current_user, params: params)
    end

    def execute(skip_system_notes: false)
      return error(_('Operation not allowed'), 403) unless @current_user.can?(authorization_action, container)

      # We should not initialize the callback classes during the build service execution because these will be
      # initialized when we call #create below
      @issue = @build_service.execute(initialize_callbacks: false)

      # issue_type and work_item_type are set in BuildService, so we can delete it from params, in later phase
      # it can be set also from quick actions
      [:issue_type, :work_item_type, :work_item_type_id].each { |attribute| params.delete(attribute) }

      handle_move_between_ids(@issue)

      @add_related_issue ||= params.delete(:add_related_issue)
      filter_resolve_discussion_params

      issue = create(@issue, skip_system_notes: skip_system_notes)

      if issue.persisted?
        success(issue: issue)
      else
        error(issue.errors.full_messages, 422, pass_back: { issue: issue })
      end
    end

    def external_author
      params[:external_author] # present when creating an issue using service desk (email: from)
    end

    def before_create(issue)
      issue.check_for_spam(user: current_user, action: :create) if perform_spam_check

      # current_user (defined in BaseService) is not available within run_after_commit block
      user = current_user
      assign_description_from_template(issue)
      issue.run_after_commit do
        NewIssueWorker.perform_async(issue.id, user.id, issue.class.to_s)
        Issues::PlacementWorker.perform_async(nil, issue.project_id)
        # issue.namespace_id can point to either a project through project namespace or a group.
        Onboarding::IssueCreatedWorker.perform_async(issue.namespace_id)
      end
    end

    # Add new items to Issues::AfterCreateService if they can be performed in Sidekiq
    def after_create(issue)
      user_agent_detail_service.create
      handle_add_related_issue(issue)
      resolve_discussions_with_issue(issue)
      handle_escalation_status_change(issue)
      create_timeline_event(issue)
      try_to_associate_contacts(issue)

      super
    end

    def handle_changes(issue, options)
      super
      old_associations = options.fetch(:old_associations, {})
      old_assignees = old_associations.fetch(:assignees, [])

      handle_assignee_changes(issue, old_assignees)
    end

    def handle_assignee_changes(issue, old_assignees)
      return if issue.assignees == old_assignees

      create_assignee_note(issue, old_assignees)
      Gitlab::ResourceEvents::AssignmentEventRecorder.new(parent: issue, old_assignees: old_assignees).record
    end

    def resolve_discussions_with_issue(issue)
      return if discussions_to_resolve.empty?

      Discussions::ResolveService.new(
        project,
        current_user,
        one_or_more_discussions: discussions_to_resolve,
        follow_up_issue: issue
      ).execute
    end

    private

    def authorization_action
      :create_issue
    end

    attr_reader :perform_spam_check, :extra_params

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

      IncidentManagement::TimelineEvents::CreateService.create_incident(issue, current_user)
    end

    def user_agent_detail_service
      UserAgentDetailService.new(spammable: @issue, perform_spam_check: perform_spam_check)
    end

    def handle_add_related_issue(issue)
      return unless @add_related_issue

      IssueLinks::CreateService.new(issue, issue.author, { target_issuable: @add_related_issue }).execute
    end

    def try_to_associate_contacts(issue)
      return unless issue.external_author
      return unless current_user.can?(:set_issue_crm_contacts, issue)

      contacts = [issue.external_author]
      contacts.concat extra_params[:cc] unless extra_params[:cc].nil?

      set_crm_contacts(issue, contacts)
    end

    def assign_description_from_template(issue)
      return if issue.description.present?

      # Find the exact name for the default template (if the project has one).
      # Since there are multiple possibilities regarding the capitalization(s) that the
      # default template file name can have, getting the exact template name here will
      # allow us to extract the contents later, and bail early if the project does not have
      # a default template
      templates = TemplateFinder.all_template_names(project, :issues)
      template = templates.values.flatten.find { |tmpl| tmpl[:name].casecmp?('default') }

      return unless template

      begin
        default_template = TemplateFinder.build(
          :issues,
          issue.project,
          {
            name: template[:name],
            source_template_project_id: issue.project.id
          }
        ).execute
      rescue ::Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
        nil
      end

      issue.description = default_template.content if default_template.present?
    end
  end
end

Issues::CreateService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

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

  let_it_be(:group) { create(:group, :crm_enabled) }
  let_it_be_with_reload(:project) { create(:project, :public, group: group) }
  let_it_be(:user) { create(:user) }

  let(:opts) { { title: 'title' } }
  let(:service) { described_class.new(container: project, current_user: user, params: opts) }

  it_behaves_like 'rate limited service' do
    let(:key) { :issues_create }
    let(:key_scope) { %i[project current_user external_author] }
    let(:application_limit_key) { :issues_create_limit }
    let(:created_model) { Issue }
  end

  describe '#execute' do
    let_it_be(:assignee) { create(:user) }
    let_it_be(:milestone) { create(:milestone, project: project) }

    let(:result) { service.execute }
    let(:issue) { result[:issue] }

    context 'when params are invalid' do
      let(:opts) { { title: '' } }

      before_all do
        project.add_guest(assignee)
      end

      it 'returns an error service response' do
        expect(result).to be_error
        expect(result.errors).to include("Title can't be blank")
        expect(issue).not_to be_persisted
      end
    end

    context 'when params are valid' do
      let_it_be(:labels) { create_pair(:label, project: project) }

      before_all do
        project.add_guest(user)
        project.add_guest(assignee)
      end

      let(:opts) do
        { title: 'Awesome issue',
          issue_type: :task,
          description: 'please fix',
          assignee_ids: [assignee.id],
          label_ids: labels.map(&:id),
          milestone_id: milestone.id,
          milestone: milestone,
          due_date: Date.tomorrow }
      end

      context 'when an unauthorized project_id is provided' do
        let(:unauthorized_project) { create(:project) }

        before do
          opts[:project_id] = unauthorized_project.id
        end

        it 'ignores the project_id param and creates issue in the given project' do
          expect(issue.project).to eq(project)
          expect(unauthorized_project.reload.issues.count).to eq(0)
        end
      end

      describe 'authorization' do
        let_it_be(:project) { create(:project, :private, group: group).tap { |project| project.add_guest(user) } }

        let(:opts) { { title: 'private issue', description: 'please fix' } }

        context 'when the user is authorized' do
          it 'allows the user to create an issue' do
            expect(result).to be_success
            expect(issue).to be_persisted
          end
        end

        context 'when the user is not authorized' do
          let(:user) { create(:user) }

          it 'does not allow the user to create an issue' do
            expect(result).to be_error
            expect(result.errors).to contain_exactly('Operation not allowed')
            expect(result.http_status).to eq(403)
            expect(issue).to be_nil
          end
        end
      end

      it 'works if base work item types were not created yet' do
        WorkItems::Type.delete_all

        expect do
          issue
        end.to change(Issue, :count).by(1)
      end

      it 'creates the issue with the given params' do
        expect(Issuable::CommonSystemNotesService).to receive_message_chain(:new, :execute)

        expect(result).to be_success
        expect(issue).to be_persisted
        expect(issue).to be_a(::Issue)
        expect(issue.title).to eq('Awesome issue')
        expect(issue.assignees).to eq([assignee])
        expect(issue.labels).to match_array(labels)
        expect(issue.milestone).to eq(milestone)
        expect(issue.due_date).to eq(Date.tomorrow)
        expect(issue.work_item_type.base_type).to eq('task')
        expect(issue.issue_customer_relations_contacts).to be_empty
      end

      context 'with milestone' do
        it 'deletes milestone issues count cache' do
          expect_next(Milestones::IssuesCountService, milestone)
            .to receive(:delete_cache).and_call_original

          expect(result).to be_success
        end
      end

      context 'when the work item type is not allowed to create' do
        before do
          allow_next_instance_of(::Issues::BuildService) do |instance|
            allow(instance).to receive(:create_issue_type_allowed?).twice.and_return(false)
          end
        end

        it 'ignores the type and creates default issue' do
          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue).to be_a(::Issue)
          expect(issue.work_item_type.base_type).to eq('issue')
        end
      end

      it 'calls NewIssueWorker with correct arguments' do
        expect(NewIssueWorker).to receive(:perform_async).with(Integer, user.id, 'Issue')

        issue
      end

      it 'calls GroupMentionWorker' do
        expect(Integrations::GroupMentionWorker).to receive(:perform_async)

        issue
      end

      context 'when a build_service is provided' do
        let(:result) { described_class.new(container: project, current_user: user, params: opts, build_service: build_service).execute }

        let(:issue_from_builder) { build(:work_item, project: project, title: 'Issue from builder') }
        let(:build_service) { double(:build_service, execute: issue_from_builder) }

        it 'uses the provided service to build the issue' do
          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue).to be_a(WorkItem)
        end
      end

      context 'when issue template is provided' do
        let_it_be(:files) { { '.gitlab/issue_templates/Default.md' => 'Default template contents' } }
        let_it_be_with_reload(:project) { create(:project, :custom_repo, group: group, files: files).tap { |project| project.add_guest(user) } }

        context 'when description is blank' do
          it 'sets template contents as description when description is blank' do
            opts[:description] = ''

            expect(result).to be_success
            expect(issue).to be_persisted
            expect(issue).to have_attributes(description: 'Default template contents')
          end
        end

        context 'when description is not blank' do
          it 'does not apply template when description is not blank' do
            expect(result).to be_success
            expect(issue).to be_persisted
            expect(issue).to have_attributes(description: 'please fix')
          end
        end
      end

      context 'when skip_system_notes is true' do
        let(:issue) { described_class.new(container: project, current_user: user, params: opts).execute(skip_system_notes: true) }

        it 'does not call Issuable::CommonSystemNotesService' do
          expect(Issuable::CommonSystemNotesService).not_to receive(:new)

          issue
        end
      end

      context 'when setting a position' do
        let(:issue_before) { create(:issue, project: project, relative_position: 10) }
        let(:issue_after) { create(:issue, project: project, relative_position: 50) }

        before do
          opts.merge!(move_between_ids: [issue_before.id, issue_after.id])
        end

        it 'sets the correct relative position' do
          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue.relative_position).to be_present
          expect(issue.relative_position).to be_between(issue_before.relative_position, issue_after.relative_position)
        end
      end

      it_behaves_like 'not an incident issue'

      context 'when issue is incident type' do
        before do
          opts.merge!(issue_type: 'incident')
        end

        let(:current_user) { user }

        subject { issue }

        context 'as reporter' do
          let_it_be(:reporter) { create(:user) }

          let(:user) { reporter }

          before_all do
            project.add_reporter(reporter)
          end

          it_behaves_like 'incident issue'

          it 'calls IncidentManagement::Incidents::CreateEscalationStatusService' do
            expect_next(::IncidentManagement::IssuableEscalationStatuses::CreateService, a_kind_of(Issue))
              .to receive(:execute)

            issue
          end

          it 'calls IncidentManagement::TimelineEvents::CreateService.create_incident' do
            expect(IncidentManagement::TimelineEvents::CreateService)
              .to receive(:create_incident)
              .with(a_kind_of(Issue), reporter)

            issue
          end

          it 'calls NewIssueWorker with correct arguments' do
            expect(NewIssueWorker).to receive(:perform_async).with(Integer, reporter.id, 'Issue')

            issue
          end

          context 'when invalid' do
            before do
              opts.merge!(title: '')
            end

            it 'does not apply an incident label prematurely' do
              expect { subject }.to not_change(LabelLink, :count).and not_change(Issue, :count)
            end
          end
        end

        context 'as guest' do
          it_behaves_like 'not an incident issue'
        end
      end

      it 'refreshes the number of open issues', :use_clean_rails_memory_store_caching do
        expect do
          issue

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

      context 'when current user cannot set issue metadata in the project' do
        let_it_be(:non_member) { create(:user) }

        it 'filters out params that cannot be set without the :set_issue_metadata permission' do
          result = described_class.new(container: project, current_user: non_member, params: opts).execute
          issue = result[:issue]

          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue.title).to eq('Awesome issue')
          expect(issue.description).to eq('please fix')
          expect(issue.assignees).to be_empty
          expect(issue.labels).to be_empty
          expect(issue.milestone).to be_nil
          expect(issue.due_date).to be_nil
        end

        it 'can create confidential issues' do
          result = described_class.new(container: project, current_user: non_member, params: opts.merge(confidential: true)).execute
          issue = result[:issue]

          expect(result).to be_success
          expect(issue.confidential).to be_truthy
        end
      end

      it 'moves the issue to the end, in an asynchronous worker' do
        expect(Issues::PlacementWorker).to receive(:perform_async).with(be_nil, Integer)

        described_class.new(container: project, current_user: user, params: opts).execute
      end

      context 'when label belongs to project group' do
        let(:group) { create(:group) }
        let(:group_labels) { create_pair(:group_label, group: group) }

        let(:opts) do
          {
            title: 'Title',
            description: 'Description',
            label_ids: group_labels.map(&:id)
          }
        end

        before do
          project.update!(group: group)
        end

        it 'assigns group labels' do
          expect(issue.labels).to match_array group_labels
        end
      end

      context 'when label belongs to different project' do
        let(:label) { create(:label) }

        let(:opts) do
          { title: 'Title',
            description: 'Description',
            label_ids: [label.id] }
        end

        it 'does not assign label' do
          expect(issue.labels).not_to include label
        end
      end

      context 'when labels is nil' do
        let(:opts) do
          { title: 'Title',
            description: 'Description',
            labels: nil }
        end

        it 'does not assign label' do
          expect(issue.labels).to be_empty
        end
      end

      context 'when labels is nil and label_ids is present' do
        let(:opts) do
          { title: 'Title',
            description: 'Description',
            labels: nil,
            label_ids: labels.map(&:id) }
        end

        it 'assigns group labels' do
          expect(issue.labels).to match_array labels
        end
      end

      context 'when milestone belongs to different project' do
        let(:milestone) { create(:milestone) }

        let(:opts) do
          { title: 'Title',
            description: 'Description',
            milestone_id: milestone.id }
        end

        it 'does not assign milestone' do
          expect(issue.milestone).not_to eq milestone
        end
      end

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

        it 'invalidates open issues counter for assignees when issue is assigned' do
          project.add_maintainer(assignee)

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

          expect(assignee.assigned_open_issues_count).to eq 1
        end

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

          issue = result.payload[:issue]
          expect(issue.assignment_events).to match([have_attributes(user_id: assignee.id, action: 'add')])
        end
      end

      context 'when duplicate label titles are given' do
        let(:label) { create(:label, project: project) }

        let(:opts) do
          { title: 'Title',
            description: 'Description',
            labels: [label.title, label.title] }
        end

        it 'assigns the label once' do
          expect(issue.labels).to contain_exactly(label)
        end
      end

      context 'when sentry identifier is given' do
        before do
          sentry_attributes = { sentry_issue_attributes: { sentry_issue_identifier: 42 } }
          opts.merge!(sentry_attributes)
        end

        it 'does not assign the sentry error' do
          expect(issue.sentry_issue).to eq(nil)
        end

        context 'user is reporter or above' do
          before do
            project.add_developer(user)
          end

          it 'assigns the sentry error' do
            expect(issue.sentry_issue).to be_kind_of(SentryIssue)
          end
        end
      end

      describe 'executing hooks' do
        let(:opts) { { title: 'Title', description: 'Description' } }
        let(:expected_payload) do
          include(
            event_type: 'issue',
            object_kind: 'issue',
            changes: {
              author_id: { current: user.id, previous: nil },
              created_at: { current: kind_of(Time), previous: nil },
              description: { current: opts[:description], previous: nil },
              id: { current: kind_of(Integer), previous: nil },
              iid: { current: kind_of(Integer), previous: nil },
              project_id: { current: project.id, previous: nil },
              title: { current: opts[:title], previous: nil },
              updated_at: { current: kind_of(Time), previous: nil },
              time_estimate: { current: 0, previous: nil }
            },
            object_attributes: include(
              opts.merge(
                author_id: user.id,
                project_id: project.id
              )
            )
          )
        end

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

          described_class.new(container: project, current_user: user, params: opts).execute
        end

        context 'when issue is confidential' do
          let(:expected_payload) do
            include(
              event_type: 'confidential_issue',
              object_kind: 'issue',
              changes: include(
                confidential: { current: true, previous: false }
              ),
              object_attributes: include(confidential: true)
            )
          end

          before do
            opts[:confidential] = true
          end

          it 'executes confidential issue hooks' do
            expect(project.project_namespace).to receive(:execute_hooks).with(expected_payload, :confidential_issue_hooks)
            expect(project.project_namespace).to receive(:execute_integrations).with(expected_payload, :confidential_issue_hooks)

            described_class.new(container: project, current_user: user, params: opts).execute
          end
        end
      end

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

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

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

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

      it 'schedules a namespace onboarding create action worker' do
        expect(Onboarding::IssueCreatedWorker).to receive(:perform_async).with(project.project_namespace_id)

        issue
      end
    end

    context 'issue create service' do
      context 'assignees' do
        before_all do
          project.add_maintainer(user)
        end

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

          result = described_class.new(container: project, current_user: user, params: opts).execute
          issue = result[:issue]

          expect(result).to be_success
          expect(issue.assignees).to be_empty
        end

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

          result = described_class.new(container: project, current_user: user, params: opts).execute
          issue = result[:issue]

          expect(result).to be_success
          expect(issue.assignees).to be_empty
        end

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

          result = described_class.new(container: project, current_user: user, params: opts).execute
          issue = result[:issue]

          expect(result).to be_success
          expect(issue.assignees).to eq([assignee])
        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: [assignee.id] }

              result = described_class.new(container: project, current_user: user, params: opts).execute
              issue = result[:issue]

              expect(result).to be_success
              expect(issue.assignees).to be_empty
            end
          end
        end
      end
    end

    it_behaves_like 'issuable record that supports quick actions' do
      let(:issuable) { described_class.new(container: project, current_user: user, params: params).execute[:issue] }
    end

    context 'Quick actions' do
      context 'with assignee, milestone, and contact in params and command' do
        let_it_be(:contact) { create(:contact, group: group) }

        let(:opts) do
          {
            assignee_ids: [create(:user).id],
            milestone_id: 1,
            title: 'Title',
            description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}"),
            add_contacts: [contact.email]
          }
        end

        before_all do
          group.add_maintainer(user)
          project.add_maintainer(assignee)
        end

        it 'assigns, sets milestone, and sets contact to issuable from command' do
          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue.assignees).to eq([assignee])
          expect(issue.milestone).to eq(milestone)
          expect(issue.issue_customer_relations_contacts.last.contact).to eq(contact)
        end
      end

      context 'with external_author' do
        let_it_be(:contact) { create(:contact, group: group) }

        context 'when CRM contact exists with matching e-mail' do
          let(:opts) do
            {
              title: 'Title',
              external_author: contact.email
            }
          end

          context 'with permission' do
            it 'assigns contact to issue' do
              group.add_reporter(user)

              expect(result).to be_success
              expect(issue).to be_persisted
              expect(issue.issue_customer_relations_contacts.last.contact).to eq(contact)
            end
          end

          context 'without permission' do
            it 'does not assign contact to issue' do
              group.add_guest(user)

              expect(result).to be_success
              expect(issue).to be_persisted
              expect(issue.issue_customer_relations_contacts).to be_empty
            end
          end
        end

        context 'when no CRM contact exists with matching e-mail' do
          let(:opts) do
            {
              title: 'Title',
              external_author: 'example@gitlab.com'
            }
          end

          it 'does not assign contact to issue' do
            group.add_reporter(user)
            expect(issue).to be_persisted
            expect(issue.issue_customer_relations_contacts).to be_empty
          end
        end
      end

      context 'with alert bot author' do
        let_it_be(:user) { Users::Internal.alert_bot }
        let_it_be(:label) { create(:label, project: project) }

        let(:opts) do
          {
            title: 'Title',
            description: %(/label #{label.to_reference(format: :name)}")
          }
        end

        it 'can apply labels' do
          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue.labels).to eq([label])
        end
      end

      context 'when using promote_to_incident' do
        let(:opts) { { title: 'Title', description: '/promote_to_incident' } }

        before do
          project.add_developer(user)
        end

        it 'creates an issue with the correct issue type' do
          expect { result }.to change(Issue, :count).by(1)

          created_issue = Issue.last

          expect(created_issue.work_item_type).to eq(WorkItems::Type.default_by_type('incident'))
        end
      end
    end

    context 'resolving discussions' do
      let_it_be(:discussion) { create(:diff_note_on_merge_request).to_discussion }
      let_it_be(:merge_request) { discussion.noteable }
      let_it_be(:project) { merge_request.source_project }

      before_all do
        project.add_maintainer(user)
      end

      describe 'for a single discussion' do
        let(:opts) { { discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid } }

        it 'resolves the discussion' do
          described_class.new(container: project, current_user: user, params: opts).execute
          discussion.first_note.reload

          expect(discussion.resolved?).to be(true)
        end

        it 'added a system note to the discussion' do
          described_class.new(container: project, current_user: user, params: opts).execute

          reloaded_discussion = MergeRequest.find(merge_request.id).discussions.first

          expect(reloaded_discussion.last_note.system).to eq(true)
        end

        it 'sets default title and description values if not provided' do
          result = described_class.new(
            container: project, current_user: user,
            params: opts
          ).execute
          issue = result[:issue]

          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue.title).to eq("Follow-up from \"#{merge_request.title}\"")
          expect(issue.description).to include("The following discussion from #{merge_request.to_reference} should be addressed")
        end

        it 'takes params from the request over the default values' do
          result = described_class.new(
            container: project,
            current_user: user,
            params: opts.merge(
              description: 'Custom issue description',
              title: 'My new issue'
            )
          ).execute
          issue = result[:issue]

          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue.description).to eq('Custom issue description')
          expect(issue.title).to eq('My new issue')
        end
      end

      describe 'for a merge request' do
        let(:opts) { { merge_request_to_resolve_discussions_of: merge_request.iid } }

        it 'resolves the discussion' do
          described_class.new(container: project, current_user: user, params: opts).execute
          discussion.first_note.reload

          expect(discussion.resolved?).to be(true)
        end

        it 'added a system note to the discussion' do
          described_class.new(container: project, current_user: user, params: opts).execute

          reloaded_discussion = MergeRequest.find(merge_request.id).discussions.first

          expect(reloaded_discussion.last_note.system).to eq(true)
        end

        it 'sets default title and description values if not provided' do
          result = described_class.new(
            container: project, current_user: user,
            params: opts
          ).execute
          issue = result[:issue]

          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue.title).to eq("Follow-up from \"#{merge_request.title}\"")
          expect(issue.description).to include("The following discussion from #{merge_request.to_reference} should be addressed")
        end

        it 'takes params from the request over the default values' do
          result = described_class.new(
            container: project,
            current_user: user,
            params: opts.merge(
              description: 'Custom issue description',
              title: 'My new issue'
            )
          ).execute
          issue = result[:issue]

          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue.description).to eq('Custom issue description')
          expect(issue.title).to eq('My new issue')
        end
      end
    end

    context 'add related issue' do
      let_it_be(:related_issue) { create(:issue, project: project) }

      let(:opts) do
        { title: 'A new issue', add_related_issue: related_issue }
      end

      it 'ignores related issue if not accessible' do
        expect { issue }.not_to change { IssueLink.count }
        expect(result).to be_success
        expect(issue).to be_persisted
      end

      context 'when user has access to the related issue' do
        before do
          project.add_developer(user)
        end

        it 'adds a link to the issue' do
          expect { issue }.to change { IssueLink.count }.by(1)
          expect(result).to be_success
          expect(issue).to be_persisted
          expect(issue.related_issues(user)).to eq([related_issue])
        end
      end
    end

    context 'checking spam' do
      let(:params) do
        {
          title: 'Spam issue'
        }
      end

      let(:perform_spam_check) { true }

      subject do
        described_class.new(container: project, current_user: user, params: params, perform_spam_check: perform_spam_check)
      end

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

        subject.execute
      end

      context 'when `perform_spam_check` is set to `false`' do
        let(:perform_spam_check) { false }

        it 'does not execute the SpamActionService' do
          expect_next_instance_of(Issue) do |instance|
            expect(instance).not_to receive(:check_for_spam)
          end

          subject.execute
        end
      end
    end
  end
end


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

module Pages
  class DestroyDeploymentsService
    def initialize(project, last_deployment_id = nil)
      @project = project
      @last_deployment_id = last_deployment_id
    end

    def execute
      deployments_to_destroy = @project.pages_deployments
      deployments_to_destroy = deployments_to_destroy.older_than(@last_deployment_id) if @last_deployment_id
      deployments_to_destroy.find_each(&:destroy) # rubocop: disable CodeReuse/ActiveRecord
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Pages::DestroyDeploymentsService, feature_category: :pages do
  let_it_be(:project) { create(:project) }
  let!(:old_deployments) { create_list(:pages_deployment, 2, project: project) }
  let!(:last_deployment) { create(:pages_deployment, project: project) }
  let!(:newer_deployment) { create(:pages_deployment, project: project) }
  let!(:deployment_from_another_project) { create(:pages_deployment) }

  it 'destroys all deployments of the project' do
    expect { described_class.new(project).execute }
      .to change { PagesDeployment.count }.by(-4)

    expect(deployment_from_another_project.reload).to be_persisted
  end

  it 'destroy only deployments older than last deployment if it is provided' do
    expect { described_class.new(project, last_deployment.id).execute }
      .to change { PagesDeployment.count }.by(-2)

    expect(last_deployment.reload).to be_persisted
    expect(newer_deployment.reload).to be_persisted
    expect(deployment_from_another_project.reload).to be_persisted
  end
end


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

module Pages
  class DeleteService < BaseService
    def execute
      PagesDeployment.deactivate_all(project)

      # project.pages_domains.delete_all will just nullify project_id:
      # > If no :dependent option is given, then it will follow the default
      # > strategy for `has_many :through` associations.
      # > The default strategy is :nullify which sets the foreign keys to NULL.
      PagesDomain.for_project(project).delete_all

      publish_deleted_event

      DestroyPagesDeploymentsWorker.perform_async(project.id)
    end

    private

    def publish_deleted_event
      event = Pages::PageDeletedEvent.new(data: {
        project_id: project.id,
        namespace_id: project.namespace_id,
        root_namespace_id: project.root_namespace.id
      })

      Gitlab::EventStore.publish(event)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Pages::DeleteService, feature_category: :pages do
  let_it_be(:admin) { create(:admin) }

  let(:project) { create(:project, path: "my.project") }
  let(:service) { described_class.new(project, admin) }

  it 'marks pages as not deployed' do
    create(:pages_deployment, project: project)

    expect { service.execute }
      .to change { project.reload.pages_deployed? }
      .from(true).to(false)
  end

  it 'deletes all domains' do
    domain = create(:pages_domain, project: project)
    unrelated_domain = create(:pages_domain)

    service.execute

    expect(PagesDomain.find_by_id(domain.id)).to eq(nil)
    expect(PagesDomain.find_by_id(unrelated_domain.id)).to be_present
  end

  it 'schedules a destruction of pages deployments' do
    expect(DestroyPagesDeploymentsWorker)
      .to(receive(:perform_async).with(project.id))

    service.execute
  end

  it 'removes pages deployments', :sidekiq_inline do
    create(:pages_deployment, project: project)

    expect { service.execute }
      .to change { PagesDeployment.count }.by(-1)
  end

  it 'publishes a ProjectDeleted event with project id and namespace id' do
    expected_data = {
      project_id: project.id,
      namespace_id: project.namespace_id,
      root_namespace_id: project.root_namespace.id
    }

    expect { service.execute }.to publish_event(Pages::PageDeletedEvent).with(expected_data)
  end
end


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

module Timelogs
  class CreateService < Timelogs::BaseService
    attr_accessor :issuable, :time_spent, :spent_at, :summary

    def initialize(issuable, time_spent, spent_at, summary, user)
      super(user)

      @issuable = issuable
      @time_spent = time_spent
      @spent_at = spent_at
      @summary = summary
    end

    def execute
      unless can?(current_user, :create_timelog, issuable)
        return error(
          _("%{issuable_class_name} doesn't exist or you don't have permission to add timelog to it.") % {
            issuable_class_name: issuable.nil? ? 'Issuable' : issuable.base_class_name
          }, 404)
      end

      return error(_("Spent at can't be a future date and time."), 404) if spent_at.future?
      return error(_("Time spent can't be zero."), 404) if time_spent == 0

      issue = issuable if issuable.is_a?(Issue)
      merge_request = issuable if issuable.is_a?(MergeRequest)

      timelog = Timelog.new(
        time_spent: time_spent,
        spent_at: spent_at,
        summary: summary,
        user: current_user,
        issue: issue,
        merge_request: merge_request,
        note: nil
      )

      if !timelog.save
        error_in_save(timelog)
      else
        SystemNoteService.created_timelog(issuable, issuable.project, current_user, timelog)
        success(timelog)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Timelogs::CreateService, feature_category: :team_planning do
  let_it_be(:author) { create(:user) }
  let_it_be(:project) { create(:project, :public) }

  let(:issuable) { nil }
  let(:users_container) { project }

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

    context 'when issuable is an Issue' do
      let_it_be(:issuable) { create(:issue, project: project) }
      let_it_be(:note_noteable) { create(:issue, project: project) }

      it_behaves_like 'issuable supports timelog creation service'
    end

    context 'when issuable is a MergeRequest' do
      let_it_be(:issuable) { create(:merge_request, source_project: project, source_branch: 'branch-1') }
      let_it_be(:note_noteable) { create(:merge_request, source_project: project, source_branch: 'branch-2') }

      it_behaves_like 'issuable supports timelog creation service'
    end

    context 'when issuable is a WorkItem' do
      let_it_be(:issuable) { create(:work_item, project: project, title: 'WorkItem-1') }
      let_it_be(:note_noteable) { create(:work_item, project: project, title: 'WorkItem-2') }

      it_behaves_like 'issuable supports timelog creation service'
    end

    context 'when issuable is an Incident' do
      let_it_be(:issuable) { create(:incident, project: project) }
      let_it_be(:note_noteable) { create(:incident, project: project) }

      it_behaves_like 'issuable supports timelog creation service'
    end
  end
end