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

module WorkItems
  class ImportCsvService < ImportCsv::BaseService
    extend ::Gitlab::Utils::Override

    FeatureNotAvailableError = StandardError.new(
      'This feature is currently behind a feature flag and it is not available.'
    )
    NotAuthorizedError = StandardError.new('You do not have permission to import work items in this project.')

    override :initialize
    def initialize(*args)
      super

      @type_errors = {
        blank: [],
        missing: {},
        disallowed: {}
      }
    end

    def self.required_headers
      %w[title type].freeze
    end

    def execute
      raise FeatureNotAvailableError if ::Feature.disabled?(:import_export_work_items_csv, project)
      raise NotAuthorizedError unless Ability.allowed?(user, :import_work_items, project)

      super
    end

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

    private

    attr_accessor :type_errors

    def create_object(attributes)
      super[:work_item]
    end

    def create_object_class
      ::WorkItems::CreateService
    end

    override :attributes_for
    def attributes_for(row)
      {
        title: row[:title],
        work_item_type: match_work_item_type(row[:type])
      }
    end

    override :validate_headers_presence!
    def validate_headers_presence!(headers)
      required_headers = self.class.required_headers

      headers.downcase!
      return if headers && required_headers.all? { |rh| headers.include?(rh) }

      required_headers_message = "Required headers are missing. Required headers are #{required_headers.join(', ')}"
      raise CSV::MalformedCSVError.new(required_headers_message, 1)
    end

    def match_work_item_type(work_item_type)
      match = available_work_item_types[work_item_type&.downcase]
      match[:type] if match
    end

    def available_work_item_types
      {
        issue: {
          allowed: Ability.allowed?(user, :create_issue, project),
          type: WorkItems::Type.default_by_type(:issue)
        }
      }.with_indifferent_access
    end
    strong_memoize_attr :available_work_item_types

    def preprocess!
      with_csv_lines.each do |row, line_no|
        work_item_type = row[:type]&.strip&.downcase

        if work_item_type.blank?
          type_errors[:blank] << line_no
        elsif missing?(work_item_type)
          # does this work item exist in the range of work items we support?
          (type_errors[:missing][work_item_type] ||= []) << line_no
        elsif !allowed?(work_item_type)
          (type_errors[:disallowed][work_item_type] ||= []) << line_no
        end
      end

      return if type_errors[:blank].empty? &&
        type_errors[:missing].blank? &&
        type_errors[:disallowed].blank?

      results[:type_errors] = type_errors
      raise PreprocessError
    end

    def missing?(work_item_type_name)
      !available_work_item_types.key?(work_item_type_name)
    end

    def allowed?(work_item_type_name)
      !!available_work_item_types[work_item_type_name][:allowed]
    end
  end
end

WorkItems::ImportCsvService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::ImportCsvService, feature_category: :team_planning do
  let_it_be(:project) { create(:project) }
  let_it_be(:user) { create(:user) }
  let_it_be(:author) { create(:user, username: 'csv_author') }
  let(:file) { fixture_file_upload('spec/fixtures/work_items_valid_types.csv') }
  let(:service) do
    uploader = FileUploader.new(project)
    uploader.store!(file)

    described_class.new(user, project, uploader)
  end

  let_it_be(:issue_type) { ::WorkItems::Type.default_issue_type }

  let(:work_items) { ::WorkItems::WorkItemsFinder.new(user, project: project).execute }
  let(:email_method) { :import_work_items_csv_email }

  subject { service.execute }

  describe '#execute', :aggregate_failures do
    context 'when user has permission' do
      before do
        project.add_reporter(user)
      end

      it_behaves_like 'importer with email notification'

      context 'when file format is valid' do
        context 'when work item types are available' do
          it 'creates the expected number of work items' do
            expect { subject }.to change { work_items.count }.by 2
          end

          it 'sets work item attributes' do
            result = subject

            expect(work_items.reload).to contain_exactly(
              have_attributes(
                title: 'Valid issue',
                work_item_type_id: issue_type.id
              ),
              have_attributes(
                title: 'Valid issue with alternate case',
                work_item_type_id: issue_type.id
              )
            )

            expect(result[:success]).to eq(2)
            expect(result[:error_lines]).to eq([])
            expect(result[:type_errors]).to be_nil
            expect(result[:parse_error]).to eq(false)
          end
        end

        context 'when csv contains work item types that are missing or not available' do
          let(:file) { fixture_file_upload('spec/fixtures/work_items_invalid_types.csv') }

          it 'creates no work items' do
            expect { subject }.not_to change { work_items.count }
          end

          it 'returns the correct result' do
            result = subject

            expect(result[:success]).to eq(0)
            expect(result[:error_lines]).to be_empty # there are problematic lines detailed below
            expect(result[:parse_error]).to eq(false)
            expect(result[:type_errors]).to match({
              blank: [4],
              disallowed: {}, # tested in the EE version
              missing: {
                "isssue" => [2],
                "issue!!" => [3]
              }
            })
          end
        end
      end

      context 'when file is missing necessary headers' do
        let(:file) { fixture_file_upload('spec/fixtures/work_items_missing_header.csv') }

        it 'creates no records' do
          result = subject

          expect(result[:success]).to eq(0)
          expect(result[:error_lines]).to eq([1])
          expect(result[:type_errors]).to be_nil
          expect(result[:parse_error]).to eq(true)
        end

        it 'creates no work items' do
          expect { subject }.not_to change { work_items.count }
        end
      end

      context 'when import_export_work_items_csv feature flag is off' do
        before do
          stub_feature_flags(import_export_work_items_csv: false)
        end

        it 'raises an error' do
          expect { subject }.to raise_error(/This feature is currently behind a feature flag and it is not available./)
        end
      end
    end

    context 'when user does not have permission' do
      before do
        project.add_guest(user)
      end

      it 'raises an error' do
        expect { subject }.to raise_error(/You do not have permission to import work items in this project/)
      end
    end
  end
end


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

module WorkItems
  class CreateFromTaskService
    def initialize(work_item:, perform_spam_check: true, current_user: nil, work_item_params: {})
      @work_item = work_item
      @current_user = current_user
      @work_item_params = work_item_params
      @perform_spam_check = perform_spam_check
      @errors = []
    end

    def execute
      transaction_result = ApplicationRecord.transaction do
        create_and_link_result = CreateAndLinkService.new(
          project: @work_item.project,
          current_user: @current_user,
          params: @work_item_params.slice(:title, :work_item_type_id),
          perform_spam_check: @perform_spam_check,
          link_params: { parent_work_item: @work_item }
        ).execute

        if create_and_link_result.error?
          @errors += create_and_link_result.errors
          raise ActiveRecord::Rollback
        end

        replacement_result = TaskListReferenceReplacementService.new(
          work_item: @work_item,
          current_user: @current_user,
          work_item_reference: create_and_link_result[:work_item].to_reference,
          line_number_start: @work_item_params[:line_number_start],
          line_number_end: @work_item_params[:line_number_end],
          title: @work_item_params[:title],
          lock_version: @work_item_params[:lock_version]
        ).execute

        if replacement_result.error?
          @errors += replacement_result.errors
          raise ActiveRecord::Rollback
        end

        create_and_link_result
      end

      return transaction_result if transaction_result

      ::ServiceResponse.error(message: @errors, http_status: 422)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::CreateFromTaskService, feature_category: :team_planning do
  let_it_be(:project) { create(:project) }
  let_it_be(:developer) { create(:user) }
  let_it_be(:list_work_item, refind: true) { create(:work_item, project: project, description: "- [ ] Item to be converted\n    second line\n    third line") }

  let(:work_item_to_update) { list_work_item }
  let(:link_params) { {} }
  let(:current_user) { developer }
  let(:params) do
    {
      title: 'Awesome work item',
      work_item_type_id: WorkItems::Type.default_by_type(:task).id,
      line_number_start: 1,
      line_number_end: 3,
      lock_version: work_item_to_update.lock_version
    }
  end

  before_all do
    project.add_developer(developer)
  end

  shared_examples 'CreateFromTask service with invalid params' do
    it { is_expected.to be_error }

    it 'does not create a work item or links' do
      expect do
        service_result
      end.to not_change(WorkItem, :count).and(
        not_change(WorkItems::ParentLink, :count)
      )
    end
  end

  describe '#execute' do
    subject(:service_result) { described_class.new(work_item: work_item_to_update, current_user: current_user, work_item_params: params).execute }

    context 'when work item params are valid' do
      it { is_expected.to be_success }

      it 'creates a work item and creates parent link to the original work item' do
        expect do
          service_result
        end.to change(WorkItem, :count).by(1).and(
          change(WorkItems::ParentLink, :count).by(1)
        )

        expect(work_item_to_update.reload.work_item_children).not_to be_empty
      end

      it 'replaces the original issue markdown description with new work item reference' do
        service_result

        created_work_item = WorkItem.last

        expect(list_work_item.description).to eq("- [ ] #{created_work_item.to_reference}+")
      end

      it_behaves_like 'title with extra spaces'
    end

    context 'when last operation fails' do
      before do
        params.merge!(line_number_start: 0)
      end

      it 'rollbacks all operations' do
        expect do
          service_result
        end.to not_change(WorkItem, :count).and(
          not_change(WorkItems::ParentLink, :count)
        )
      end

      it { is_expected.to be_error }

      it 'returns an error message' do
        expect(service_result.errors).to contain_exactly('line_number_start must be greater than 0')
      end
    end

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

      it_behaves_like 'CreateFromTask service with invalid params'

      it 'returns work item errors' do
        expect(service_result.errors).to contain_exactly("Title can't be blank")
      end
    end
  end
end


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

module WorkItems
  class TaskListReferenceRemovalService
    STALE_OBJECT_MESSAGE = 'Stale work item. Check lock version'

    def initialize(work_item:, task:, line_number_start:, line_number_end:, lock_version:, current_user:)
      @work_item = work_item
      @task = task
      @line_number_start = line_number_start
      @line_number_end = line_number_end
      @lock_version = lock_version
      @current_user = current_user
      @task_reference = /#{Regexp.escape(@task.to_reference)}(?!\d)\+/
    end

    def execute
      return ::ServiceResponse.error(message: 'line_number_start must be greater than 0') if @line_number_start < 1
      return ::ServiceResponse.error(message: "Work item description can't be blank") if @work_item.description.blank?

      if @line_number_end < @line_number_start
        return ::ServiceResponse.error(message: 'line_number_end must be greater or equal to line_number_start')
      end

      source_lines = @work_item.description.split("\n")

      line_matches_reference = (@line_number_start..@line_number_end).any? do |line_number|
        markdown_line = source_lines[line_number - 1]

        if @task_reference.match?(markdown_line)
          markdown_line.sub!(@task_reference, @task.title)
        end
      end

      unless line_matches_reference
        return ::ServiceResponse.error(
          message: "Unable to detect a task on lines #{@line_number_start}-#{@line_number_end}"
        )
      end

      ::WorkItems::UpdateService.new(
        container: @work_item.project,
        current_user: @current_user,
        params: { description: source_lines.join("\n"), lock_version: @lock_version }
      ).execute(@work_item)

      if @work_item.valid?
        ::ServiceResponse.success
      else
        ::ServiceResponse.error(message: @work_item.errors.full_messages)
      end
    rescue ActiveRecord::StaleObjectError
      ::ServiceResponse.error(message: STALE_OBJECT_MESSAGE)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::TaskListReferenceRemovalService, feature_category: :team_planning do
  let_it_be(:developer) { create(:user) }
  let_it_be(:project) { create(:project, :repository).tap { |project| project.add_developer(developer) } }
  let_it_be(:task) { create(:work_item, project: project, title: 'Task title') }
  let_it_be(:single_line_work_item, refind: true) do
    create(:work_item, project: project, description: "- [ ] #{task.to_reference}+ single line")
  end

  let_it_be(:multiple_line_work_item, refind: true) do
    create(
      :work_item,
      project: project,
      description: <<~MARKDOWN
        Any text

        * [ ] Item to be converted
            #{task.to_reference}+ second line
            third line
        * [x] task

        More text
      MARKDOWN
    )
  end

  let(:line_number_start) { 3 }
  let(:line_number_end) { 5 }
  let(:work_item) { multiple_line_work_item }
  let(:lock_version) { work_item.lock_version }

  shared_examples 'successful work item task reference removal service' do |expected_description|
    it { is_expected.to be_success }

    it 'removes the task list item containing the task reference' do
      expect do
        result
      end.to change(work_item, :description).from(work_item.description).to(expected_description)
    end

    it 'creates system notes' do
      expect do
        result
      end.to change(Note, :count).by(1)

      expect(Note.last.note).to include('changed the description')
    end
  end

  shared_examples 'failing work item task reference removal service' do |error_message|
    it { is_expected.to be_error }

    it 'does not change the work item description' do
      expect do
        result
        work_item.reload
      end.to not_change(work_item, :description)
    end

    it 'returns an error message' do
      expect(result.errors).to contain_exactly(error_message)
    end
  end

  describe '#execute' do
    subject(:result) do
      described_class.new(
        work_item: work_item,
        task: task,
        line_number_start: line_number_start,
        line_number_end: line_number_end,
        lock_version: lock_version,
        current_user: developer
      ).execute
    end

    context 'when task mardown spans a single line' do
      let(:line_number_start) { 1 }
      let(:line_number_end) { 1 }
      let(:work_item) { single_line_work_item }

      it_behaves_like 'successful work item task reference removal service', '- [ ] Task title single line'

      context 'when description does not contain a task' do
        let_it_be(:no_matching_work_item) { create(:work_item, project: project, description: 'no matching task') }

        let(:work_item) { no_matching_work_item }

        it_behaves_like 'failing work item task reference removal service', 'Unable to detect a task on lines 1-1'
      end

      context 'when description reference does not exactly match the task reference' do
        before do
          work_item.update!(description: work_item.description.gsub(task.to_reference, "#{task.to_reference}200"))
        end

        it_behaves_like 'failing work item task reference removal service', 'Unable to detect a task on lines 1-1'
      end
    end

    context 'when task mardown spans multiple lines' do
      it_behaves_like 'successful work item task reference removal service',
        "Any text\n\n* [ ] Item to be converted\n    Task title second line\n    third line\n* [x] task\n\nMore text"
    end

    context 'when updating the work item fails' do
      before do
        work_item.title = nil
      end

      it_behaves_like 'failing work item task reference removal service', "Title can't be blank"
    end

    context 'when description is empty' do
      let_it_be(:empty_work_item) { create(:work_item, project: project, description: '') }

      let(:work_item) { empty_work_item }

      it_behaves_like 'failing work item task reference removal service', "Work item description can't be blank"
    end

    context 'when line_number_start is lower than 1' do
      let(:line_number_start) { 0 }

      it_behaves_like 'failing work item task reference removal service', 'line_number_start must be greater than 0'
    end

    context 'when line_number_end is lower than line_number_start' do
      let(:line_number_end) { line_number_start - 1 }

      it_behaves_like 'failing work item task reference removal service',
        'line_number_end must be greater or equal to line_number_start'
    end

    context 'when lock_version is older than current' do
      let(:lock_version) { work_item.lock_version - 1 }

      it_behaves_like 'failing work item task reference removal service', 'Stale work item. Check lock version'
    end

    context 'when work item is stale before updating' do
      it_behaves_like 'failing work item task reference removal service', 'Stale work item. Check lock version' do
        before do
          ::WorkItem.where(id: work_item.id).update_all(lock_version: lock_version + 1)
        end
      end
    end
  end
end


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

module WorkItems
  class BuildService < ::Issues::BuildService
    private

    def model_klass
      ::WorkItem
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::BuildService, feature_category: :team_planning do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:guest) { create(:user) }

  let(:user) { guest }

  before_all do
    project.add_guest(guest)
  end

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

    it { is_expected.to be_a(::WorkItem) }
  end
end


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

module WorkItems
  # Create and link operations are not run inside a transaction in this class
  # because CreateFromTaskService also creates a transaction.
  # This class should always be run inside a transaction as we could end up with
  # new work items that were never associated with other work items as expected.
  class CreateAndLinkService
    def initialize(project:, perform_spam_check: true, current_user: nil, params: {}, link_params: {})
      @project = project
      @current_user = current_user
      @params = params
      @link_params = link_params
      @perform_spam_check = perform_spam_check
    end

    def execute
      create_result = CreateService.new(
        container: @project,
        current_user: @current_user,
        params: @params.merge(title: @params[:title].strip).reverse_merge(confidential: confidential_parent),
        perform_spam_check: @perform_spam_check
      ).execute
      return create_result if create_result.error?

      work_item = create_result[:work_item]
      return ::ServiceResponse.success(payload: payload(work_item)) if @link_params.blank?

      result = WorkItems::ParentLinks::CreateService.new(
        @link_params[:parent_work_item],
        @current_user,
        { target_issuable: work_item }
      ).execute

      if result[:status] == :success
        ::ServiceResponse.success(payload: payload(work_item))
      else
        ::ServiceResponse.error(message: result[:message], http_status: 404)
      end
    end

    private

    def confidential_parent
      !!@link_params[:parent_work_item]&.confidential
    end

    def payload(work_item)
      { work_item: work_item }
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::CreateAndLinkService, feature_category: :portfolio_management do
  let_it_be(:group) { create(:group) }
  let_it_be(:project) { create(:project, group: group) }
  let_it_be(:user) { create(:user) }
  let_it_be(:related_work_item, refind: true) { create(:work_item, project: project) }
  let_it_be(:invalid_parent) { create(:work_item, :task, project: project) }

  let(:link_params) { {} }

  let(:params) do
    {
      title: 'Awesome work item',
      description: 'please fix',
      work_item_type_id: WorkItems::Type.default_by_type(:task).id
    }
  end

  before_all do
    project.add_developer(user)
  end

  shared_examples 'successful work item and link creator' do
    it 'creates a work item successfully with links' do
      expect do
        service_result
      end.to change(WorkItem, :count).by(1).and(
        change(WorkItems::ParentLink, :count).by(1)
      )
    end

    it 'copies confidential status from the parent' do
      expect do
        service_result
      end.to change(WorkItem, :count).by(1)

      created_task = WorkItem.last

      expect(created_task.confidential).to eq(related_work_item.confidential)
    end
  end

  describe '#execute' do
    subject(:service_result) { described_class.new(project: project, current_user: user, params: params, link_params: link_params).execute }

    context 'when work item params are valid' do
      it { is_expected.to be_success }

      it 'creates a work item successfully with no links' do
        expect do
          service_result
        end.to change(WorkItem, :count).by(1).and(
          not_change(IssueLink, :count)
        )
      end

      it_behaves_like 'title with extra spaces'

      context 'when link params are valid' do
        let(:link_params) { { parent_work_item: related_work_item } }

        context 'when parent is not confidential' do
          it_behaves_like 'successful work item and link creator'
        end

        context 'when parent is confidential' do
          before do
            related_work_item.update!(confidential: true)
          end

          it_behaves_like 'successful work item and link creator'
        end
      end

      context 'when link creation fails' do
        let(:link_params) { { parent_work_item: invalid_parent } }

        it { is_expected.to be_error }

        it 'does not create a link and does not rollback transaction' do
          expect do
            service_result
          end.to not_change(WorkItems::ParentLink, :count).and(
            change(WorkItem, :count).by(1)
          )
        end

        it 'returns a link creation error message' do
          expect(service_result.errors).to contain_exactly(/is not allowed to add this type of parent/)
        end
      end
    end

    context 'when work item params are invalid' do
      let(:params) do
        {
          title: '',
          description: 'invalid work item'
        }
      end

      it { is_expected.to be_error }

      it 'does not create a work item or links' do
        expect do
          service_result
        end.to not_change(WorkItem, :count).and(
          not_change(WorkItems::ParentLink, :count)
        )
      end

      it 'returns work item errors' do
        expect(service_result.errors).to contain_exactly("Title can't be blank")
      end
    end
  end
end


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

module WorkItems
  class UpdateService < ::Issues::UpdateService
    extend Gitlab::Utils::Override
    include WidgetableService

    def initialize(container:, current_user: nil, params: {}, perform_spam_check: false, widget_params: {})
      params[:widget_params] = true if widget_params.present?

      super(container: container, current_user: current_user, params: params, perform_spam_check: perform_spam_check)

      @widget_params = widget_params
    end

    def execute(work_item)
      updated_work_item = super

      if updated_work_item.valid?
        success(payload(work_item))
      else
        error(updated_work_item.errors.full_messages, :unprocessable_entity, pass_back: payload(updated_work_item))
      end
    rescue ::WorkItems::Widgets::BaseService::WidgetError => e
      error(e.message, :unprocessable_entity)
    end

    private

    override :handle_quick_actions
    def handle_quick_actions(work_item)
      # Do not handle quick actions unless the work item is the default Issue.
      # The available quick actions for a work item depend on its type and widgets.
      return unless work_item.work_item_type.default_issue?

      super
    end

    def prepare_update_params(work_item)
      execute_widgets(
        work_item: work_item,
        callback: :prepare_update_params,
        widget_params: @widget_params,
        service_params: params
      )

      super
    end

    def before_update(work_item, skip_spam_check: false)
      execute_widgets(work_item: work_item, callback: :before_update_callback, widget_params: @widget_params)

      super
    end

    def transaction_update(work_item, opts = {})
      execute_widgets(work_item: work_item, callback: :before_update_in_transaction, widget_params: @widget_params)

      super
    end

    override :after_update
    def after_update(work_item, old_associations)
      super

      GraphqlTriggers.issuable_title_updated(work_item) if work_item.previous_changes.key?(:title)
    end

    def payload(work_item)
      { work_item: work_item }
    end

    def handle_label_changes(issuable, old_labels)
      return false unless super

      Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter.track_work_item_labels_changed_action(
        author: current_user
      )
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
  let_it_be(:developer) { create(:user) }
  let_it_be(:guest) { create(:user) }
  let_it_be(:project) { create(:project) }
  let_it_be(:parent) { create(:work_item, project: project) }
  let_it_be_with_reload(:work_item) { create(:work_item, project: project, assignees: [developer]) }

  let(:widget_params) { {} }
  let(:opts) { {} }
  let(:current_user) { developer }

  before do
    project.add_developer(developer)
    project.add_guest(guest)
  end

  describe '#execute' do
    let(:service) do
      described_class.new(
        container: project,
        current_user: current_user,
        params: opts,
        widget_params: widget_params
      )
    end

    subject(:update_work_item) { service.execute(work_item) }

    shared_examples 'update service that triggers graphql dates updated subscription' do
      it 'triggers graphql subscription issueableDatesUpdated' do
        expect(GraphqlTriggers).to receive(:issuable_dates_updated).with(work_item).and_call_original

        update_work_item
      end
    end

    context 'when applying quick actions' do
      let(:opts) { { description: "/shrug" } }

      context 'when work item type is not the default Issue' do
        before do
          task_type = WorkItems::Type.default_by_type(:task)
          work_item.update_columns(work_item_type_id: task_type.id)
        end

        it 'does not apply the quick action' do
          expect do
            update_work_item
          end.to change(work_item, :description).to('/shrug')
        end
      end

      context 'when work item type is the default Issue' do
        let(:issue) { create(:work_item, description: '') }

        it 'applies the quick action' do
          expect do
            update_work_item
          end.to change(work_item, :description).to(' ¯\＿(ツ)＿/¯')
        end
      end
    end

    context 'when title is changed' do
      let(:opts) { { title: 'changed' } }

      it 'triggers issuable_title_updated graphql subscription' do
        expect(GraphqlTriggers).to receive(:issuable_title_updated).with(work_item).and_call_original
        expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter).to receive(:track_work_item_title_changed_action).with(author: current_user)
        # During the work item transition we also want to track work items as issues
        expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_title_changed_action)
        expect(update_work_item[:status]).to eq(:success)
      end

      it_behaves_like 'internal event tracking' do
        let(:event) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_TITLE_CHANGED }
        let(:user) { current_user }
        let(:namespace) { project.namespace }
        subject(:service_action) { update_work_item[:status] }
      end

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

    context 'when title is not changed' do
      let(:opts) { { description: 'changed' } }

      it 'does not trigger issuable_title_updated graphql subscription' do
        expect(GraphqlTriggers).not_to receive(:issuable_title_updated)
        expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter).not_to receive(:track_work_item_title_changed_action)
        expect(update_work_item[:status]).to eq(:success)
      end

      it 'does not emit Snowplow event', :snowplow do
        expect_no_snowplow_event

        update_work_item
      end
    end

    context 'when dates are changed' do
      let(:opts) { { start_date: Date.today } }

      it 'tracks users updating work item dates' do
        expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter).to receive(:track_work_item_date_changed_action).with(author: current_user)

        update_work_item
      end

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

    context 'when decription is changed' do
      let(:opts) { { description: 'description changed' } }

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

        update_work_item
      end

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

    context 'when decription is not changed' do
      let(:opts) { { title: 'title changed' } }

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

        update_work_item
      end
    end

    context 'when updating state_event' do
      context 'when state_event is close' do
        let(:opts) { { state_event: 'close' } }

        it 'closes the work item' do
          expect do
            update_work_item
            work_item.reload
          end.to change(work_item, :state).from('opened').to('closed')
        end
      end

      context 'when state_event is reopen' do
        let(:opts) { { state_event: 'reopen' } }

        before do
          work_item.close!
        end

        it 'reopens the work item' do
          expect do
            update_work_item
            work_item.reload
          end.to change(work_item, :state).from('closed').to('opened')
        end
      end
    end

    it_behaves_like 'work item widgetable service' do
      let(:widget_params) do
        {
          hierarchy_widget: { parent: parent },
          description_widget: { description: 'foo' }
        }
      end

      let(:service) do
        described_class.new(
          container: project,
          current_user: current_user,
          params: opts,
          widget_params: widget_params
        )
      end

      let(:service_execute) { service.execute(work_item) }

      let(:supported_widgets) do
        [
          { klass: WorkItems::Widgets::DescriptionService::UpdateService, callback: :before_update_callback, params: { description: 'foo' } },
          { klass: WorkItems::Widgets::HierarchyService::UpdateService, callback: :before_update_in_transaction, params: { parent: parent } }
        ]
      end
    end

    context 'when updating widgets' do
      let(:widget_service_class) { WorkItems::Widgets::DescriptionService::UpdateService }
      let(:widget_params) { { description_widget: { description: 'changed' } } }

      context 'when widget service is not present' do
        before do
          allow(widget_service_class).to receive(:new).and_return(nil)
        end

        it 'ignores widget param' do
          expect { update_work_item }.not_to change(work_item, :description)
        end
      end

      context 'when the widget does not support update callback' do
        before do
          allow_next_instance_of(widget_service_class) do |instance|
            allow(instance)
              .to receive(:before_update_callback)
              .with(params: { description: 'changed' }).and_return(nil)
          end
        end

        it 'ignores widget param' do
          expect { update_work_item }.not_to change(work_item, :description)
        end
      end

      context 'for the description widget' do
        it 'updates the description of the work item' do
          update_work_item

          expect(work_item.description).to eq('changed')
        end

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

        context 'with mentions', :mailer, :sidekiq_might_not_need_inline do
          shared_examples 'creates the todo and sends email' do |attribute|
            it 'creates a todo and sends email' do
              expect { perform_enqueued_jobs { update_work_item } }.to change(Todo, :count).by(1)
              expect(work_item.reload.attributes[attribute.to_s]).to eq("mention #{guest.to_reference}")
              should_email(guest)
            end
          end

          context 'when description contains a user mention' do
            let(:widget_params) { { description_widget: { description: "mention #{guest.to_reference}" } } }

            it_behaves_like 'creates the todo and sends email', :description
          end

          context 'when title contains a user mention' do
            let(:opts) { { title: "mention #{guest.to_reference}" } }

            it_behaves_like 'creates the todo and sends email', :title
          end
        end

        context 'when work item validation fails' do
          let(:opts) { { title: '' } }

          it 'returns validation errors' do
            expect(update_work_item[:message]).to contain_exactly("Title can't be blank")
          end

          it 'does not execute after-update widgets', :aggregate_failures do
            expect(service).to receive(:update).and_call_original
            expect(service).not_to receive(:execute_widgets).with(callback: :update, widget_params: widget_params)

            expect { update_work_item }.not_to change(work_item, :description)
          end
        end
      end

      context 'for start and due date widget' do
        let(:updated_date) { 1.week.from_now.to_date }

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

          it_behaves_like 'update service that triggers graphql dates updated subscription'
        end

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

          it_behaves_like 'update service that triggers graphql dates updated subscription'
        end

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

          it 'does not trigger date updated subscription' do
            expect(GraphqlTriggers).not_to receive(:issuable_dates_updated)

            update_work_item
          end
        end
      end

      context 'for the hierarchy widget' do
        let(:opts) { { title: 'changed' } }
        let_it_be(:child_work_item) { create(:work_item, :task, project: project) }

        let(:widget_params) { { hierarchy_widget: { children: [child_work_item] } } }

        it 'updates the children of the work item' do
          expect do
            update_work_item
            work_item.reload
          end.to change(WorkItems::ParentLink, :count).by(1)

          expect(work_item.work_item_children).to include(child_work_item)
        end

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

        context 'when child type is invalid' do
          let_it_be(:child_work_item) { create(:work_item, project: project) }

          it 'returns error status' do
            expect(subject[:status]).to be(:error)
            expect(subject[:message])
              .to match("#{child_work_item.to_reference} cannot be added: is not allowed to add this type of parent")
          end

          it 'does not update work item attributes' do
            expect do
              update_work_item
              work_item.reload
            end.to not_change(WorkItems::ParentLink, :count).and(not_change(work_item, :title))
          end
        end

        context 'when work item validation fails' do
          let(:opts) { { title: '' } }

          it 'returns validation errors' do
            expect(update_work_item[:message]).to contain_exactly("Title can't be blank")
          end

          it 'does not execute after-update widgets', :aggregate_failures do
            expect(service).to receive(:update).and_call_original
            expect(service).not_to receive(:execute_widgets).with(callback: :before_update_in_transaction, widget_params: widget_params)
            expect(work_item.work_item_children).not_to include(child_work_item)

            update_work_item
          end
        end
      end

      context 'for milestone widget' do
        let_it_be(:milestone) { create(:milestone, project: project) }

        let(:widget_params) { { milestone_widget: { milestone_id: milestone.id } } }

        context 'when milestone is updated' do
          it "triggers 'issuableMilestoneUpdated'" do
            expect(work_item.milestone).to eq(nil)
            expect(GraphqlTriggers).to receive(:issuable_milestone_updated).with(work_item).and_call_original

            update_work_item
          end

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

        context 'when milestone remains unchanged' do
          before do
            update_work_item
          end

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

            update_work_item
          end
        end
      end

      context 'for current user todos widget' do
        let_it_be(:user_todo) { create(:todo, target: work_item, user: developer, project: project, state: :pending) }
        let_it_be(:other_todo) { create(:todo, target: work_item, user: create(:user), project: project, state: :pending) }

        context 'when action is mark_as_done' do
          let(:widget_params) { { current_user_todos_widget: { action: 'mark_as_done' } } }

          it 'marks current user todo as done' do
            expect do
              update_work_item
              user_todo.reload
              other_todo.reload
            end.to change(user_todo, :state).from('pending').to('done').and not_change { other_todo.state }
          end

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

        context 'when action is add' do
          let(:widget_params) { { current_user_todos_widget: { action: 'add' } } }

          it 'adds a ToDo for the work item' do
            expect do
              update_work_item
              work_item.reload
            end.to change(Todo, :count).by(1)
          end
        end
      end
    end

    describe 'label updates' do
      let_it_be(:label1) { create(:label, project: project) }
      let_it_be(:label2) { create(:label, project: project) }

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

        it 'tracks users updating work item labels' do
          expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter).to receive(:track_work_item_labels_changed_action).with(author: current_user)

          update_work_item
        end

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

        it_behaves_like 'broadcasting issuable labels updates' do
          let(:issuable) { work_item }
          let(:label_a) { label1 }
          let(:label_b) { label2 }

          def update_issuable(update_params)
            described_class.new(
              container: project,
              current_user: current_user,
              params: update_params,
              widget_params: widget_params
            ).execute(work_item)
          end
        end
      end

      context 'when labels are not changed' do
        shared_examples 'work item update that does not track label updates' do
          it 'does not track users updating work item labels' do
            expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter).not_to receive(:track_work_item_labels_changed_action)

            update_work_item
          end
        end

        context 'when labels param is not provided' do
          let(:opts) { { title: 'not updating labels' } }

          it_behaves_like 'work item update that does not track label updates'
        end

        context 'when labels param is provided but labels remain unchanged' do
          let(:opts) { { label_ids: [] } }

          it_behaves_like 'work item update that does not track label updates'
        end

        context 'when labels param is provided invalid values' do
          let(:opts) { { label_ids: [non_existing_record_id] } }

          it_behaves_like 'work item update that does not track label updates'
        end
      end
    end
  end
end


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

module WorkItems
  class CreateService < Issues::CreateService
    extend ::Gitlab::Utils::Override
    include WidgetableService

    def initialize(container:, perform_spam_check: true, current_user: nil, params: {}, widget_params: {})
      super(
        container: container,
        current_user: current_user,
        params: params,
        perform_spam_check: perform_spam_check,
        build_service: ::WorkItems::BuildService.new(container: container, current_user: current_user, params: params)
      )
      @widget_params = widget_params
    end

    def execute
      result = super
      return result if result.error?

      work_item = result[:issue]

      if work_item.valid?
        success(payload(work_item))
      else
        error(work_item.errors.full_messages, :unprocessable_entity, pass_back: payload(work_item))
      end
    rescue ::WorkItems::Widgets::BaseService::WidgetError => e
      error(e.message, :unprocessable_entity)
    end

    def before_create(work_item)
      execute_widgets(
        work_item: work_item,
        callback: :before_create_callback,
        widget_params: @widget_params
      )

      super
    end

    def transaction_create(work_item)
      super.tap do |save_result|
        if save_result
          execute_widgets(
            work_item: work_item,
            callback: :after_create_in_transaction,
            widget_params: @widget_params
          )
        end
      end
    end

    private

    override :handle_quick_actions
    def handle_quick_actions(work_item)
      # Do not handle quick actions unless the work item is the default Issue.
      # The available quick actions for a work item depend on its type and widgets.
      return if work_item.work_item_type != WorkItems::Type.default_by_type(:issue)

      super
    end

    def authorization_action
      :create_work_item
    end

    def payload(work_item)
      { work_item: work_item }
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

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

  RSpec.shared_examples 'creates work item in container' do |container_type|
    let_it_be_with_reload(:project) { create(:project) }
    let_it_be_with_reload(:group) { create(:group) }

    let_it_be(:container) do
      case container_type
      when :project then project
      when :project_namespace then project.project_namespace
      when :group then group
      end
    end

    let_it_be(:container_args) do
      case container_type
      when :project, :project_namespace then { project: project }
      when :group then { namespace: group }
      end
    end

    let_it_be(:parent) { create(:work_item, **container_args) }
    let_it_be(:guest) { create(:user) }
    let_it_be(:reporter) { create(:user) }
    let_it_be(:user_with_no_access) { create(:user) }

    let(:widget_params) { {} }
    let(:perform_spam_check) { false }
    let(:current_user) { guest }
    let(:opts) do
      {
        title: 'Awesome work_item',
        description: 'please fix'
      }
    end

    before_all do
      memberships_container = container.is_a?(Namespaces::ProjectNamespace) ? container.reload.project : container
      memberships_container.add_guest(guest)
      memberships_container.add_reporter(reporter)
    end

    describe '#execute' do
      shared_examples 'fails creating work item and returns errors' do
        it 'does not create new work item if parent can not be set' do
          expect { service_result }.not_to change(WorkItem, :count)

          expect(service_result[:status]).to be(:error)
          expect(service_result[:message]).to match(error_message)
        end
      end

      let(:service) do
        described_class.new(
          container: container,
          current_user: current_user,
          params: opts,
          perform_spam_check: perform_spam_check,
          widget_params: widget_params
        )
      end

      subject(:service_result) { service.execute }

      context 'when user is not allowed to create a work item in the container' do
        let(:current_user) { user_with_no_access }

        it { is_expected.to be_error }

        it 'returns an access error' do
          expect(service_result.errors).to contain_exactly('Operation not allowed')
        end
      end

      context 'when applying quick actions' do
        let(:work_item) { service_result[:work_item] }
        let(:opts) do
          {
            title: 'My work item',
            work_item_type: work_item_type,
            description: '/shrug'
          }
        end

        context 'when work item type is not the default Issue' do
          let(:work_item_type) { create(:work_item_type, :task, namespace: group) }

          it 'saves the work item without applying the quick action' do
            expect(service_result).to be_success
            expect(work_item).to be_persisted
            expect(work_item.description).to eq('/shrug')
          end
        end

        context 'when work item type is the default Issue' do
          let(:work_item_type) { WorkItems::Type.default_by_type(:issue) }

          it 'saves the work item and applies the quick action' do
            expect(service_result).to be_success
            expect(work_item).to be_persisted
            expect(work_item.description).to eq(' ¯\＿(ツ)＿/¯')
          end
        end
      end

      context 'when params are valid' do
        it 'created instance is a WorkItem' do
          expect(Issuable::CommonSystemNotesService).to receive_message_chain(:new, :execute)

          work_item = service_result[:work_item]

          expect(work_item).to be_persisted
          expect(work_item).to be_a(::WorkItem)
          expect(work_item.title).to eq('Awesome work_item')
          expect(work_item.description).to eq('please fix')
          expect(work_item.work_item_type.base_type).to eq('issue')
        end

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

          service_result
        end
      end

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

        it { is_expected.to be_error }

        it 'returns validation errors' do
          expect(service_result.errors).to contain_exactly("Title can't be blank")
        end

        it 'does not execute after-create transaction widgets' do
          expect(service).to receive(:create).and_call_original
          expect(service).not_to receive(:execute_widgets)
                                   .with(callback: :after_create_in_transaction, widget_params: widget_params)

          service_result
        end
      end

      context 'checking spam' do
        let(:perform_spam_check) { true }

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

          service_result
        end

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

          it 'does not check for spam' do
            expect_next_instance_of(WorkItem) do |instance|
              expect(instance).not_to receive(:check_for_spam)
            end

            service_result
          end
        end
      end

      it_behaves_like 'work item widgetable service' do
        let(:widget_params) do
          {
            hierarchy_widget: { parent: parent }
          }
        end

        let(:service) do
          described_class.new(
            container: container,
            current_user: current_user,
            params: opts,
            widget_params: widget_params
          )
        end

        let(:service_execute) { service.execute }

        let(:supported_widgets) do
          [
            {
              klass: WorkItems::Widgets::HierarchyService::CreateService,
              callback: :after_create_in_transaction,
              params: { parent: parent }
            }
          ]
        end
      end

      describe 'hierarchy widget' do
        let(:widget_params) { { hierarchy_widget: { parent: parent } } }

        context 'when user can admin parent link' do
          let(:current_user) { reporter }

          context 'when parent is valid work item' do
            let(:opts) do
              {
                title: 'Awesome work_item',
                description: 'please fix',
                work_item_type: WorkItems::Type.default_by_type(:task)
              }
            end

            it 'creates new work item and sets parent reference' do
              expect { service_result }.to change(WorkItem, :count).by(1).and(
                change(WorkItems::ParentLink, :count).by(1)
              )

              expect(service_result[:status]).to be(:success)
            end
          end

          context 'when parent type is invalid' do
            let_it_be(:parent) { create(:work_item, :task, **container_args) }

            it_behaves_like 'fails creating work item and returns errors' do
              let(:error_message) { 'is not allowed to add this type of parent' }
            end
          end
        end

        context 'when user cannot admin parent link' do
          let(:current_user) { guest }

          let(:opts) do
            {
              title: 'Awesome work_item',
              description: 'please fix',
              work_item_type: WorkItems::Type.default_by_type(:task)
            }
          end

          it_behaves_like 'fails creating work item and returns errors' do
            let(:error_message) { 'No matching work item found. Make sure that you are adding a valid work item ID.' }
          end
        end
      end
    end
  end

  it_behaves_like 'creates work item in container', :project
  it_behaves_like 'creates work item in container', :project_namespace
  it_behaves_like 'creates work item in container', :group
end


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

module WorkItems
  class DeleteService < Issuable::DestroyService
    def execute(work_item)
      unless current_user.can?(:delete_work_item, work_item)
        return ::ServiceResponse.error(message: 'User not authorized to delete work item')
      end

      if super
        ::ServiceResponse.success
      else
        ::ServiceResponse.error(message: work_item.errors.full_messages)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::DeleteService, feature_category: :team_planning do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:guest) { create(:user) }
  let_it_be(:work_item, refind: true) { create(:work_item, project: project, author: guest) }

  let(:user) { guest }

  before_all do
    project.add_guest(guest)
    # note necessary to test note removal as part of work item deletion
    create(:note, project: project, noteable: work_item)
  end

  describe '#execute' do
    subject(:result) { described_class.new(container: project, current_user: user).execute(work_item) }

    context 'when user can delete the work item' do
      it { is_expected.to be_success }

      # currently we don't expect destroy to fail. Mocking here for coverage and keeping
      # the service's return type consistent
      context 'when there are errors preventing to delete the work item' do
        before do
          allow(work_item).to receive(:destroy).and_return(false)
          work_item.errors.add(:title)
        end

        it { is_expected.to be_error }

        it 'returns error messages' do
          expect(result.errors).to contain_exactly('Title is invalid')
        end
      end
    end

    context 'when user cannot delete the work item' do
      let(:user) { create(:user) }

      it { is_expected.to be_error }

      it 'returns error messages' do
        expect(result.errors).to contain_exactly('User not authorized to delete work item')
      end
    end
  end
end


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

module WorkItems
  module Callbacks
    class AwardEmoji < Base
      def before_update
        return unless params.present? && params.key?(:name) && params.key?(:action)
        return unless has_permission?(:award_emoji)

        execute_emoji_service(params[:action], params[:name])
      end

      private

      def execute_emoji_service(action, name)
        class_name = {
          add: ::AwardEmojis::AddService,
          remove: ::AwardEmojis::DestroyService,
          toggle: ::AwardEmojis::ToggleService
        }

        raise_error(invalid_action_error(action)) unless class_name.key?(action)

        result = class_name[action].new(work_item, name, current_user).execute

        raise_error(result[:message]) if result[:status] == :error
      end

      def invalid_action_error(key)
        format(_("%{key} is not a valid action."), key: key)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::Callbacks::AwardEmoji, feature_category: :team_planning do
  let_it_be(:reporter) { create(:user) }
  let_it_be(:unauthorized_user) { create(:user) }
  let_it_be(:project) { create(:project, :private) }
  let_it_be(:work_item) { create(:work_item, project: project) }

  let(:current_user) { reporter }

  before_all do
    project.add_reporter(reporter)
  end

  describe '#before_update' do
    subject do
      described_class.new(issuable: work_item, current_user: current_user, params: params)
        .before_update
    end

    shared_examples 'raises a WidgetError' do
      it { expect { subject }.to raise_error(::WorkItems::Widgets::BaseService::WidgetError, message) }
    end

    context 'when awarding an emoji' do
      let(:params) { { action: :add, name: 'star' } }

      context 'when user has no access' do
        let(:current_user) { unauthorized_user }

        it 'does not award the emoji' do
          expect { subject }.not_to change { AwardEmoji.count }
        end
      end

      context 'when user has access' do
        it 'awards the emoji to the work item' do
          expect { subject }.to change { AwardEmoji.count }.by(1)

          emoji = AwardEmoji.last

          expect(emoji.name).to eq('star')
          expect(emoji.awardable_id).to eq(work_item.id)
          expect(emoji.user).to eq(current_user)
        end

        context 'when the name is incorrect' do
          let(:params) { { action: :add, name: 'foo' } }

          it_behaves_like 'raises a WidgetError' do
            let(:message) { 'Name is not a valid emoji name' }
          end
        end

        context 'when the action is incorrect' do
          let(:params) { { action: :foo, name: 'star' } }

          it_behaves_like 'raises a WidgetError' do
            let(:message) { 'foo is not a valid action.' }
          end
        end
      end
    end

    context 'when removing emoji' do
      let(:params) { { action: :remove, name: 'thumbsup' } }

      context 'when user has no access' do
        let(:current_user) { unauthorized_user }

        it 'does not remove the emoji' do
          expect { subject }.not_to change { AwardEmoji.count }
        end
      end

      context 'when user has access' do
        it 'removes existing emoji' do
          create(:award_emoji, :upvote, awardable: work_item, user: current_user)

          expect { subject }.to change { AwardEmoji.count }.by(-1)
        end

        context 'when work item does not have the emoji' do
          let(:params) { { action: :remove, name: 'star' } }

          it_behaves_like 'raises a WidgetError' do
            let(:message) { 'User has not awarded emoji of type star on the awardable' }
          end
        end
      end
    end
  end
end


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

module WorkItems
  module RelatedWorkItemLinks
    class DestroyService < BaseService
      def initialize(work_item, user, params)
        @work_item = work_item
        @current_user = user
        @params = params.dup
        @failed_ids = []
        @removed_ids = []
      end

      def execute
        return error(_('No work item found.'), 403) unless can?(current_user, :admin_work_item_link, work_item)
        return error(_('No work item IDs provided.'), 409) if params[:item_ids].empty?

        destroy_links_for(params[:item_ids])

        if removed_ids.any?
          success(message: response_message, items_removed: removed_ids, items_with_errors: failed_ids.flatten)
        else
          error(error_message)
        end
      end

      private

      attr_reader :work_item, :current_user, :failed_ids, :removed_ids

      def destroy_links_for(item_ids)
        destroy_links(source: work_item, target: item_ids, direction: :target)
        destroy_links(source: item_ids, target: work_item, direction: :source)
      end

      def destroy_links(source:, target:, direction:)
        WorkItems::RelatedWorkItemLink.for_source_and_target(source, target).each do |link|
          linked_item = link.try(direction)

          if can?(current_user, :admin_work_item_link, linked_item)
            link.destroy!
            removed_ids << linked_item.id
            create_notes(link)
          else
            failed_ids << linked_item.id
          end
        end
      end

      def create_notes(link)
        SystemNoteService.unrelate_issuable(link.source, link.target, current_user)
        SystemNoteService.unrelate_issuable(link.target, link.source, current_user)
      end

      def error_message
        not_linked = params[:item_ids] - (removed_ids + failed_ids)
        error_messages = []

        if failed_ids.any?
          error_messages << format(
            _('%{item_ids} could not be removed due to insufficient permissions'), item_ids: failed_ids.to_sentence
          )
        end

        if not_linked.any?
          error_messages << format(
            _('%{item_ids} could not be removed due to not being linked'), item_ids: not_linked.to_sentence
          )
        end

        return '' unless error_messages.any?

        format(_('IDs with errors: %{error_messages}.'), error_messages: error_messages.join(', '))
      end

      def response_message
        success_message = format(_('Successfully unlinked IDs: %{item_ids}.'), item_ids: removed_ids.to_sentence)

        return success_message unless error_message.present?

        "#{success_message} #{error_message}"
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::RelatedWorkItemLinks::DestroyService, feature_category: :portfolio_management do
  describe '#execute' do
    let_it_be(:project) { create(:project_empty_repo, :private) }
    let_it_be(:other_project) { create(:project_empty_repo, :private) }
    let_it_be(:user) { create(:user) }
    let_it_be(:source) { create(:work_item, project: project) }
    let_it_be(:linked_item1) { create(:work_item, project: project) }
    let_it_be(:linked_item2) { create(:work_item, project: project) }
    let_it_be(:no_access_item) { create(:work_item, project: other_project) }
    let_it_be(:not_linked_item) { create(:work_item, project: project) }

    let_it_be(:link1) { create(:work_item_link, source: source, target: linked_item1) }
    let_it_be(:link2) { create(:work_item_link, source: source, target: linked_item2) }
    let_it_be(:link3) { create(:work_item_link, source: source, target: no_access_item) }

    let(:ids_to_remove) { [linked_item1.id, linked_item2.id, no_access_item.id, not_linked_item.id] }

    subject(:destroy_links) { described_class.new(source, user, { item_ids: ids_to_remove }).execute }

    context 'when user can `admin_work_item_link` for the work item' do
      before_all do
        project.add_guest(user)
      end

      it 'removes existing linked items with access' do
        expect { destroy_links }.to change { WorkItems::RelatedWorkItemLink.count }.by(-2)
      end

      it 'creates notes for the source and target of each removed link' do
        [linked_item1, linked_item2].each do |item|
          expect(SystemNoteService).to receive(:unrelate_issuable).with(source, item, user)
          expect(SystemNoteService).to receive(:unrelate_issuable).with(item, source, user)
        end

        destroy_links
      end

      it 'returns correct response message' do
        message = "Successfully unlinked IDs: #{linked_item1.id} and #{linked_item2.id}. IDs with errors: " \
                  "#{no_access_item.id} could not be removed due to insufficient permissions, " \
                  "#{not_linked_item.id} could not be removed due to not being linked."

        is_expected.to eq(
          status: :success,
          message: message,
          items_removed: [linked_item1.id, linked_item2.id],
          items_with_errors: [no_access_item.id]
        )
      end

      context 'when all items fail' do
        let(:ids_to_remove) { [no_access_item.id] }
        let(:params) { { item_ids: [no_access_item.id] } }
        let(:error_msg) { "IDs with errors: #{ids_to_remove[0]} could not be removed due to insufficient permissions." }

        it 'returns an error response' do
          expect { destroy_links }.not_to change { WorkItems::RelatedWorkItemLink.count }

          is_expected.to eq(status: :error, message: error_msg)
        end
      end

      context 'when item_ids is empty' do
        let(:ids_to_remove) { [] }

        it 'returns error response' do
          is_expected.to eq(message: 'No work item IDs provided.', status: :error, http_status: 409)
        end
      end
    end

    context 'when user cannot `admin_work_item_link` for the work item' do
      it 'returns error response' do
        is_expected.to eq(message: 'No work item found.', status: :error, http_status: 403)
      end
    end
  end
end


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

module WorkItems
  module RelatedWorkItemLinks
    class CreateService < IssuableLinks::CreateService
      extend ::Gitlab::Utils::Override

      def execute
        return error(_('No matching work item found.'), 404) unless can?(current_user, :admin_work_item_link, issuable)

        response = super
        create_notes_async if new_links.any?

        if response[:status] == :success
          response[:message] = format(
            _('Successfully linked ID(s): %{item_ids}.'),
            item_ids: linked_ids(response[:created_references]).to_sentence
          )
        end

        response
      end

      def linkable_issuables(work_items)
        @linkable_issuables ||= work_items.select { |work_item| can_link_item?(work_item) }
      end

      def previous_related_issuables
        @related_issues ||= issuable.linked_work_items(authorize: false).to_a
      end

      private

      def create_notes(_issuable_link)
        # no-op notes are created asynchronously
      end

      def link_class
        WorkItems::RelatedWorkItemLink
      end

      def can_link_item?(work_item)
        return true if can?(current_user, :admin_work_item_link, work_item)

        @errors << format(
          _("Item with ID: %{id} cannot be added. You don't have permission to perform this action."),
          id: work_item.id
        )

        false
      end

      def linked_ids(created_links)
        created_links.collect(&:target_id)
      end

      def create_notes_async
        link_ids = new_links.collect(&:id)

        worker_params = {
          issuable_class: issuable.class.name,
          issuable_id: issuable.id,
          link_ids: link_ids,
          link_type: params[:link_type] || 'relates_to',
          user_id: current_user.id
        }

        Issuable::RelatedLinksCreateWorker.perform_async(worker_params)
      end

      override :issuables_already_assigned_message
      def issuables_already_assigned_message
        _('Items are already linked')
      end

      override :issuables_not_found_message
      def issuables_not_found_message
        _('No matching work item found. Make sure you are adding a valid ID and you have access to the item.')
      end
    end
  end
end

WorkItems::RelatedWorkItemLinks::CreateService.prepend_mod_with('WorkItems::RelatedWorkItemLinks::CreateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::RelatedWorkItemLinks::CreateService, feature_category: :portfolio_management do
  describe '#execute' do
    let_it_be(:user) { create(:user) }
    let_it_be(:group) { create(:group) }
    let_it_be(:project) { create(:project, group: group) }
    let_it_be(:issuable) { create(:work_item, project: project) }
    let_it_be(:issuable2) { create(:work_item, project: project) }
    let_it_be(:restricted_issuable) { create(:work_item) }
    let_it_be(:another_project) { create(:project, group: group) }
    let_it_be(:issuable3) { create(:work_item, project: another_project) }
    let_it_be(:issuable_a) { create(:work_item, project: project) }
    let_it_be(:issuable_b) { create(:work_item, project: project) }
    let_it_be(:issuable_link) { create(:work_item_link, source: issuable, target: issuable_b) }

    let(:issuable_parent) { issuable.project }
    let(:issuable_type) { 'work item' }
    let(:issuable_link_class) { WorkItems::RelatedWorkItemLink }
    let(:params) { {} }

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

    it_behaves_like 'issuable link creation', use_references: false do
      let(:response_keys) { [:status, :created_references, :message] }
      let(:async_notes) { true }
      let(:already_assigned_error_msg) { "Items are already linked" }
      let(:no_found_error_msg) do
        'No matching work item found. Make sure you are adding a valid ID and you have access to the item.'
      end
    end
  end
end


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

module WorkItems
  module ParentLinks
    class BaseService < IssuableLinks::CreateService
      extend ::Gitlab::Utils::Override

      private

      def set_parent(issuable, work_item)
        link = WorkItems::ParentLink.for_work_item(work_item)
        link.work_item_parent = issuable
        link
      end

      def create_notes(work_item)
        SystemNoteService.relate_work_item(issuable, work_item, current_user)
      end

      def linkable_issuables(work_items)
        @linkable_issuables ||= if can_admin_link?(issuable)
                                  work_items.select { |work_item| linkable?(work_item) }
                                else
                                  []
                                end
      end

      def linkable?(work_item)
        can_admin_link?(work_item) && previous_related_issuables.exclude?(work_item)
      end

      def can_admin_link?(work_item)
        can?(current_user, :admin_parent_link, work_item)
      end

      override :previous_related_issuables
      def previous_related_issuables
        @previous_related_issuables ||= issuable.work_item_children.to_a
      end

      override :target_issuable_type
      def target_issuable_type
        'work item'
      end

      override :issuables_not_found_message
      def issuables_not_found_message
        format(_('No matching %{issuable} found. Make sure that you are adding a valid %{issuable} ID.'),
          issuable: target_issuable_type)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

module WorkItems
  class ParentLinksService < WorkItems::ParentLinks::BaseService; end
end

RSpec.describe WorkItems::ParentLinks::BaseService, feature_category: :portfolio_management do
  let_it_be(:user) { create(:user) }
  let_it_be(:project) { create(:project) }
  let_it_be(:work_item) { create(:work_item, :objective, project: project) }
  let_it_be(:target_work_item) { create(:work_item, :objective, project: project) }

  let(:params) { { target_issuable: target_work_item } }
  let(:described_class_descendant) { WorkItems::ParentLinksService }

  before do
    project.add_reporter(user)
  end

  describe '#execute' do
    subject { described_class_descendant.new(work_item, user, params).execute }

    context 'when user has sufficient permissions' do
      it 'raises NotImplementedError' do
        expect { subject }.to raise_error(NotImplementedError)
      end
    end
  end
end


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

module WorkItems
  module ParentLinks
    class ReorderService < WorkItems::ParentLinks::BaseService
      private

      override :relate_issuables
      def relate_issuables(work_item)
        notes_are_expected = work_item.work_item_parent != issuable
        link = set_parent(issuable, work_item)
        reorder(link, params[:adjacent_work_item], params[:relative_position])

        create_notes(work_item) if link.save && notes_are_expected

        link
      end

      def reorder(link, adjacent_work_item, relative_position)
        WorkItems::ParentLink.move_nulls_to_end(RelativePositioning.mover.context(link).relative_siblings)

        link.move_before(adjacent_work_item.parent_link) if relative_position == 'BEFORE'
        link.move_after(adjacent_work_item.parent_link) if relative_position == 'AFTER'
      end

      override :render_conflict_error?
      def render_conflict_error?
        return false if params[:adjacent_work_item] && params[:relative_position]

        super
      end

      override :linkable?
      def linkable?(work_item)
        can_admin_link?(work_item)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::ParentLinks::ReorderService, feature_category: :portfolio_management do
  describe '#execute' do
    let_it_be(:reporter) { create(:user) }
    let_it_be(:guest) { create(:user) }
    let_it_be(:project) { create(:project) }
    let_it_be_with_reload(:parent) { create(:work_item, :objective, project: project) }
    let_it_be_with_reload(:work_item) { create(:work_item, :objective, project: project) }
    let_it_be_with_reload(:top_adjacent) { create(:work_item, :objective, project: project) }
    let_it_be_with_reload(:last_adjacent) { create(:work_item, :objective, project: project) }

    let(:parent_link_class) { WorkItems::ParentLink }
    let(:user) { reporter }
    let(:params) { { target_issuable: work_item } }
    let(:relative_range) { [top_adjacent, last_adjacent].map(&:parent_link).map(&:relative_position) }

    subject { described_class.new(parent, user, params).execute }

    before do
      project.add_reporter(reporter)
      project.add_guest(guest)

      create(:parent_link, work_item: top_adjacent, work_item_parent: parent)
      create(:parent_link, work_item: last_adjacent, work_item_parent: parent)
    end

    shared_examples 'raises a service error' do |message, status = 409|
      it { is_expected.to eq(service_error(message, http_status: status)) }
    end

    shared_examples 'returns not found error' do
      it 'returns error' do
        error = "No matching work item found. Make sure that you are adding a valid work item ID."

        is_expected.to eq(service_error(error))
      end

      it 'creates no relationship' do
        expect { subject }.not_to change { parent_link_class.count }
      end
    end

    shared_examples 'returns conflict error' do
      it_behaves_like 'raises a service error', 'Work item(s) already assigned'

      it 'creates no relationship' do
        expect { subject }.to not_change { parent_link_class.count }
      end
    end

    shared_examples 'processes ordered hierarchy' do
      it 'returns success status and processed links', :aggregate_failures do
        expect(subject.keys).to match_array([:status, :created_references])
        expect(subject[:status]).to eq(:success)
        expect(subject[:created_references].map(&:work_item_id)).to match_array([work_item.id])
      end

      it 'orders hierarchy' do
        subject

        expect(last_adjacent.parent_link.relative_position).to be_between(*relative_range)
      end
    end

    context 'when user has insufficient permissions' do
      let(:user) { guest }

      it_behaves_like 'returns not found error'

      context 'when user is a guest assigned to the work item' do
        before do
          work_item.assignees = [guest]
        end

        it_behaves_like 'returns not found error'
      end
    end

    context 'when child and parent are already linked' do
      before do
        create(:parent_link, work_item: work_item, work_item_parent: parent)
      end

      it_behaves_like 'returns conflict error'

      context 'when adjacents are already in place and the user has sufficient permissions' do
        let(:base_param) { { target_issuable: work_item } }

        shared_examples 'updates hierarchy order without notes' do
          it_behaves_like 'processes ordered hierarchy'

          it 'keeps relationships', :aggregate_failures do
            expect { subject }.to not_change { parent_link_class.count }

            expect(parent_link_class.where(work_item: work_item).last.work_item_parent).to eq(parent)
          end

          it 'does not create notes', :aggregate_failures do
            expect { subject }.to not_change { work_item.notes.count }.and(not_change { work_item.notes.count })
          end
        end

        context 'when moving before adjacent work item' do
          let(:params) { base_param.merge({ adjacent_work_item: last_adjacent, relative_position: 'BEFORE' }) }

          it_behaves_like 'updates hierarchy order without notes'
        end

        context 'when moving after adjacent work item' do
          let(:params) { base_param.merge({ adjacent_work_item: top_adjacent, relative_position: 'AFTER' }) }

          it_behaves_like 'updates hierarchy order without notes'
        end
      end
    end

    context 'when new parent is assigned' do
      shared_examples 'updates hierarchy order and creates notes' do
        it_behaves_like 'processes ordered hierarchy'

        it 'creates notes', :aggregate_failures do
          subject

          expect(parent.notes.last.note).to eq("added #{work_item.to_reference} as child objective")
          expect(work_item.notes.last.note).to eq("added #{parent.to_reference} as parent objective")
        end
      end

      context 'when adjacents are already in place and the user has sufficient permissions' do
        let(:base_param) { { target_issuable: work_item } }

        context 'when moving before adjacent work item' do
          let(:params) { base_param.merge({ adjacent_work_item: last_adjacent, relative_position: 'BEFORE' }) }

          it_behaves_like 'updates hierarchy order and creates notes'
        end

        context 'when moving after adjacent work item' do
          let(:params) { base_param.merge({ adjacent_work_item: top_adjacent, relative_position: 'AFTER' }) }

          it_behaves_like 'updates hierarchy order and creates notes'
        end

        context 'when previous parent was in place' do
          before do
            create(:parent_link, work_item: work_item,
              work_item_parent: create(:work_item, :objective, project: project))
          end

          context 'when moving before adjacent work item' do
            let(:params) { base_param.merge({ adjacent_work_item: last_adjacent, relative_position: 'BEFORE' }) }

            it_behaves_like 'updates hierarchy order and creates notes'
          end

          context 'when moving after adjacent work item' do
            let(:params) { base_param.merge({ adjacent_work_item: top_adjacent, relative_position: 'AFTER' }) }

            it_behaves_like 'updates hierarchy order and creates notes'
          end
        end
      end
    end
  end

  def service_error(message, http_status: 404)
    {
      message: message,
      status: :error,
      http_status: http_status
    }
  end
end


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

module WorkItems
  module ParentLinks
    class DestroyService < IssuableLinks::DestroyService
      attr_reader :link, :current_user, :parent, :child

      def initialize(link, user)
        @link = link
        @current_user = user
        @parent = link.work_item_parent
        @child = link.work_item
      end

      private

      def create_notes
        unrelate_note = SystemNoteService.unrelate_work_item(parent, child, current_user)

        ResourceLinkEvent.create(
          user: @current_user,
          work_item: @link.work_item_parent,
          child_work_item: @link.work_item,
          action: ResourceLinkEvent.actions[:remove],
          system_note_metadata_id: unrelate_note&.system_note_metadata&.id
        )
      end

      def not_found_message
        _('No Work Item Link found')
      end

      def permission_to_remove_relation?
        can?(current_user, :admin_parent_link, child) && can?(current_user, :admin_parent_link, parent)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::ParentLinks::DestroyService, feature_category: :team_planning do
  describe '#execute' do
    let_it_be(:reporter) { create(:user) }
    let_it_be(:guest) { create(:user) }
    let_it_be(:project) { create(:project) }
    let_it_be(:work_item) { create(:work_item, project: project) }
    let_it_be(:task) { create(:work_item, :task, project: project) }
    let_it_be(:parent_link) { create(:parent_link, work_item: task, work_item_parent: work_item) }

    let(:parent_link_class) { WorkItems::ParentLink }

    subject { described_class.new(parent_link, user).execute }

    before do
      project.add_reporter(reporter)
      project.add_guest(guest)
    end

    context 'when user has permissions to update work items' do
      let(:user) { reporter }

      it 'removes relation and creates notes', :aggregate_failures do
        expect { subject }
          .to change(parent_link_class, :count).by(-1)
          .and change(WorkItems::ResourceLinkEvent, :count).by(1)

        expect(work_item.notes.last.note).to eq("removed child task #{task.to_reference}")
        expect(task.notes.last.note).to eq("removed parent issue #{work_item.to_reference}")
        expect(WorkItems::ResourceLinkEvent.last).to have_attributes(
          user_id: user.id,
          issue_id: work_item.id,
          child_work_item_id: task.id,
          action: "remove",
          system_note_metadata_id: task.notes.last.system_note_metadata.id
        )
      end

      it 'returns success message' do
        is_expected.to eq(message: 'Relation was removed', status: :success)
      end

      context 'when note creation fails for some reason' do
        [Note.new, nil].each do |unrelate_child_note|
          it 'still records the link event', :aggregate_failures do
            allow(SystemNoteService).to receive(:unrelate_work_item).and_return(unrelate_child_note)

            expect { subject }
              .to change(WorkItems::ResourceLinkEvent, :count).by(1)
              .and not_change(Note, :count)

            expect(WorkItems::ResourceLinkEvent.last).to have_attributes(
              user_id: user.id,
              issue_id: work_item.id,
              child_work_item_id: task.id,
              action: "remove",
              system_note_metadata_id: nil
            )
          end
        end
      end
    end

    context 'when user has insufficient permissions' do
      let(:user) { guest }

      it 'does not remove relation', :aggregate_failures do
        expect { subject }
          .to not_change(parent_link_class, :count).from(1)
          .and not_change(WorkItems::ResourceLinkEvent, :count)
        expect(SystemNoteService).not_to receive(:unrelate_work_item)
      end

      it 'returns error message' do
        is_expected.to eq(message: 'No Work Item Link found', status: :error, http_status: 404)
      end
    end
  end
end


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

module WorkItems
  module ParentLinks
    class CreateService < WorkItems::ParentLinks::BaseService
      private

      override :relate_issuables
      def relate_issuables(work_item)
        link = set_parent(issuable, work_item)

        link.move_to_end

        if link.changed? && link.save
          relate_child_note = create_notes(work_item)

          ResourceLinkEvent.create(
            user: current_user,
            work_item: link.work_item_parent,
            child_work_item: link.work_item,
            action: ResourceLinkEvent.actions[:add],
            system_note_metadata_id: relate_child_note&.system_note_metadata&.id
          )
        end

        link
      end

      override :extract_references
      def extract_references
        params[:issuable_references]
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe WorkItems::ParentLinks::CreateService, feature_category: :portfolio_management do
  describe '#execute' do
    let_it_be(:user) { create(:user) }
    let_it_be(:guest) { create(:user) }
    let_it_be(:project) { create(:project) }
    let_it_be(:work_item) { create(:work_item, project: project) }
    let_it_be(:task) { create(:work_item, :task, project: project) }
    let_it_be_with_reload(:task1) { create(:work_item, :task, project: project) }
    let_it_be_with_reload(:task2) { create(:work_item, :task, project: project) }
    let_it_be(:guest_task) { create(:work_item, :task) }
    let_it_be(:invalid_task) { build_stubbed(:work_item, :task, id: non_existing_record_id) }
    let_it_be(:another_project) { (create :project) }
    let_it_be(:other_project_task) { create(:work_item, :task, iid: 100, project: another_project) }
    let_it_be(:existing_parent_link) { create(:parent_link, work_item: task, work_item_parent: work_item) }

    let(:parent_link_class) { WorkItems::ParentLink }
    let(:issuable_type) { :task }
    let(:params) { {} }

    before do
      project.add_reporter(user)
      project.add_guest(guest)
      guest_task.project.add_guest(user)
      another_project.add_reporter(user)
    end

    shared_examples 'returns not found error' do
      it 'returns error' do
        error = "No matching work item found. Make sure that you are adding a valid work item ID."

        is_expected.to eq(service_error(error))
      end

      it 'no relationship is created' do
        expect { subject }.not_to change(parent_link_class, :count)
      end
    end

    subject { described_class.new(work_item, user, params).execute }

    context 'when the reference list is empty' do
      let(:params) { { issuable_references: [] } }

      it_behaves_like 'returns not found error'
    end

    context 'when work item not found' do
      let(:params) { { issuable_references: [invalid_task] } }

      it_behaves_like 'returns not found error'
    end

    context 'when user has no permission to link work items' do
      let(:params) { { issuable_references: [guest_task] } }

      it_behaves_like 'returns not found error'
    end

    context 'child and parent are the same work item' do
      let(:params) { { issuable_references: [work_item] } }

      it 'no relationship is created' do
        expect { subject }.not_to change(parent_link_class, :count)
      end
    end

    context 'when adjacent is already in place' do
      using RSpec::Parameterized::TableSyntax

      let_it_be_with_reload(:parent_item) { create(:work_item, :objective, project: project) }
      let_it_be_with_reload(:current_item) { create(:work_item, :objective, project: project) }

      let_it_be_with_reload(:adjacent) do
        create(:work_item, :objective, project: project)
      end

      let_it_be_with_reload(:link_to_adjacent) do
        create(:parent_link, work_item_parent: parent_item, work_item: adjacent)
      end

      subject { described_class.new(parent_item, user, { target_issuable: current_item }).execute }

      where(:adjacent_position, :expected_order) do
        -100 | lazy { [adjacent, current_item] }
        0    | lazy { [adjacent, current_item] }
        100  | lazy { [adjacent, current_item] }
      end

      with_them do
        before do
          link_to_adjacent.update!(relative_position: adjacent_position)
        end

        it 'sets relative positions' do
          expect { subject }.to change(parent_link_class, :count).by(1)
          expect(parent_item.work_item_children_by_relative_position).to eq(expected_order)
        end
      end
    end

    context 'when there are tasks to relate' do
      let(:params) { { issuable_references: [task1, task2] } }

      it 'creates relationships', :aggregate_failures do
        expect { subject }.to change(parent_link_class, :count).by(2)

        tasks_parent = parent_link_class.where(work_item: [task1, task2]).map(&:work_item_parent).uniq
        expect(tasks_parent).to match_array([work_item])
      end

      it 'returns success status and created links', :aggregate_failures do
        expect(subject.keys).to match_array([:status, :created_references])
        expect(subject[:status]).to eq(:success)
        expect(subject[:created_references].map(&:work_item_id)).to match_array([task1.id, task2.id])
      end

      it 'creates notes and records the events', :aggregate_failures do
        expect { subject }.to change(WorkItems::ResourceLinkEvent, :count).by(2)

        work_item_notes = work_item.notes.last(2)
        resource_link_events = WorkItems::ResourceLinkEvent.last(2)
        expect(work_item_notes.first.note).to eq("added #{task1.to_reference} as child task")
        expect(work_item_notes.last.note).to eq("added #{task2.to_reference} as child task")
        expect(task1.notes.last.note).to eq("added #{work_item.to_reference} as parent issue")
        expect(task2.notes.last.note).to eq("added #{work_item.to_reference} as parent issue")
        expect(resource_link_events.first).to have_attributes(
          user_id: user.id,
          issue_id: work_item.id,
          child_work_item_id: task1.id,
          action: "add",
          system_note_metadata_id: task1.notes.last.system_note_metadata.id
        )
        expect(resource_link_events.last).to have_attributes(
          user_id: user.id,
          issue_id: work_item.id,
          child_work_item_id: task2.id,
          action: "add",
          system_note_metadata_id: task2.notes.last.system_note_metadata.id
        )
      end

      context 'when note creation fails for some reason' do
        let(:params) { { issuable_references: [task1] } }

        [Note.new, nil].each do |relate_child_note|
          it 'still records the link event', :aggregate_failures do
            allow_next_instance_of(WorkItems::ParentLinks::CreateService) do |instance|
              allow(instance).to receive(:create_notes).and_return(relate_child_note)
            end

            expect { subject }
              .to change(WorkItems::ResourceLinkEvent, :count).by(1)
              .and not_change(Note, :count)

            expect(WorkItems::ResourceLinkEvent.last).to have_attributes(
              user_id: user.id,
              issue_id: work_item.id,
              child_work_item_id: task1.id,
              action: "add",
              system_note_metadata_id: nil
            )
          end
        end
      end

      context 'when task is already assigned' do
        let(:params) { { issuable_references: [task, task2] } }

        it 'creates links only for non related tasks', :aggregate_failures do
          expect { subject }
            .to change(parent_link_class, :count).by(1)
            .and change(WorkItems::ResourceLinkEvent, :count).by(1)

          expect(subject[:created_references].map(&:work_item_id)).to match_array([task2.id])
          expect(work_item.notes.last.note).to eq("added #{task2.to_reference} as child task")
          expect(task2.notes.last.note).to eq("added #{work_item.to_reference} as parent issue")
          expect(task.notes).to be_empty
          expect(WorkItems::ResourceLinkEvent.last).to have_attributes(
            user_id: user.id,
            issue_id: work_item.id,
            child_work_item_id: task2.id,
            action: "add",
            system_note_metadata_id: task2.notes.last.system_note_metadata.id
          )
        end
      end

      context 'when there are invalid children' do
        let_it_be(:issue) { create(:work_item, project: project) }

        let(:params) { { issuable_references: [task1, issue, other_project_task] } }

        it 'creates links only for valid children' do
          expect { subject }.to change { parent_link_class.count }.by(1)
        end

        it 'returns error status' do
          error = "#{issue.to_reference} cannot be added: is not allowed to add this type of parent. " \
            "#{other_project_task.to_reference} cannot be added: parent must be in the same project or group as child."

          is_expected.to eq(service_error(error, http_status: 422))
        end

        it 'creates notes for valid links' do
          subject

          expect(work_item.notes.last.note).to eq("added #{task1.to_reference} as child task")
          expect(task1.notes.last.note).to eq("added #{work_item.to_reference} as parent issue")
          expect(issue.notes).to be_empty
          expect(other_project_task.notes).to be_empty
        end
      end

      context 'when parent type is invalid' do
        let(:work_item) { create :work_item, :task, project: project }

        let(:params) { { target_issuable: task1 } }

        it 'returns error status' do
          error = "#{task1.to_reference} cannot be added: is not allowed to add this type of parent"

          is_expected.to eq(service_error(error, http_status: 422))
        end
      end

      context 'when max depth is reached' do
        let(:params) { { issuable_references: [task2] } }

        before do
          stub_const("#{parent_link_class}::MAX_CHILDREN", 1)
        end

        it 'returns error status' do
          error = "#{task2.to_reference} cannot be added: parent already has maximum number of children."

          is_expected.to eq(service_error(error, http_status: 422))
        end
      end

      context 'when params include invalid ids' do
        let(:params) { { issuable_references: [task1, guest_task] } }

        it 'creates links only for valid IDs' do
          expect { subject }.to change(parent_link_class, :count).by(1)
        end
      end

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

        it_behaves_like 'returns not found error'
      end

      context 'when user is a guest assigned to the work item' do
        let(:user) { guest }

        before do
          work_item.assignees = [guest]
        end

        it_behaves_like 'returns not found error'
      end
    end
  end

  def service_error(message, http_status: 404)
    {
      message: message,
      status: :error,
      http_status: http_status
    }
  end
end