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

module SystemNotes
  class DesignManagementService < ::SystemNotes::BaseService
    include ActionView::RecordIdentifier

    # Parameters:
    #   - version [DesignManagement::Version]
    #
    # Example Note text:
    #
    #   "added [1 designs](link-to-version)"
    #   "changed [2 designs](link-to-version)"
    #
    # Returns [Array<Note>]: the created Note objects
    def design_version_added(version)
      events = DesignManagement::Action.events
      link_href = designs_path(version: version.id)

      version.designs_by_event.map do |(event_name, designs)|
        note_data = self.class.design_event_note_data(events[event_name])
        icon_name = note_data[:icon]
        n = designs.size

        body = "%s [%d %s](%s)" % [note_data[:past_tense], n, 'design'.pluralize(n), link_href]

        create_note(NoteSummary.new(noteable, project, author, body, action: icon_name))
      end
    end

    # Called when a new discussion is created on a design
    #
    # discussion_note - DiscussionNote
    #
    # Example Note text:
    #
    #   "started a discussion on screen.png"
    #
    # Returns the created Note object
    def design_discussion_added(discussion_note)
      design = discussion_note.noteable

      body = _('started a discussion on %{design_link}') % {
        design_link: '[%s](%s)' % [
          design.filename,
          designs_path(vueroute: design.filename, anchor: dom_id(discussion_note))
        ]
      }

      action = :designs_discussion_added

      create_note(NoteSummary.new(noteable, project, author, body, action: action))
    end

    # Take one of the `DesignManagement::Action.events` and
    # return:
    #   * an English past-tense verb.
    #   * the name of an icon used in renderin a system note
    #
    # We do not currently internationalize our system notes,
    # instead we just produce English-language descriptions.
    # See: https://gitlab.com/gitlab-org/gitlab/issues/30408
    # See: https://gitlab.com/gitlab-org/gitlab/issues/14056
    def self.design_event_note_data(event)
      case event
      when DesignManagement::Action.events[:creation]
        { icon: 'designs_added', past_tense: 'added' }
      when DesignManagement::Action.events[:modification]
        { icon: 'designs_modified', past_tense: 'updated' }
      when DesignManagement::Action.events[:deletion]
        { icon: 'designs_removed', past_tense: 'removed' }
      else
        raise "Unknown event: #{event}"
      end
    end

    private

    def designs_path(params = {})
      url_helpers.designs_project_issue_path(project, noteable, params)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe SystemNotes::DesignManagementService, feature_category: :design_management do
  let(:project) { create(:project) }
  let(:issue) { create(:issue, project: project) }

  let(:instance) { described_class.new(noteable: instance_noteable, project: instance_project, author: instance_author) }

  describe '#design_version_added' do
    let(:instance_noteable) { version.issue }
    let(:instance_project) { version.issue.project }
    let(:instance_author) { version.author }

    subject { instance.design_version_added(version) }

    # default (valid) parameters:
    let(:n_designs) { 3 }
    let(:designs) { create_list(:design, n_designs, issue: issue) }
    let(:user) { build(:user) }
    let(:version) do
      create(:design_version, issue: issue, designs: designs)
    end

    before do
      # Avoid needing to call into gitaly
      allow(version).to receive(:author).and_return(user)
    end

    context 'with one kind of event' do
      before do
        DesignManagement::Action
          .where(design: designs).update_all(event: :modification)
      end

      it 'makes just one note' do
        expect(subject).to contain_exactly(Note)
      end

      it 'adds a new system note' do
        expect { subject }.to change { Note.system.count }.by(1)
      end
    end

    context 'with a mixture of events' do
      let(:n_designs) { DesignManagement::Action.events.size }

      before do
        designs.each_with_index do |design, i|
          design.actions.update_all(event: i)
        end
      end

      it 'makes one note for each kind of event' do
        expect(subject).to have_attributes(size: n_designs)
      end

      it 'adds a system note for each kind of event' do
        expect { subject }.to change { Note.system.count }.by(n_designs)
      end
    end

    describe 'icons' do
      where(:action) do
        [
          [:creation],
          [:modification],
          [:deletion]
        ]
      end

      with_them do
        before do
          version.actions.update_all(event: action)
        end

        subject(:metadata) do
          instance.design_version_added(version)
            .first.system_note_metadata
        end

        it 'has a valid action' do
          expect(::SystemNoteHelper::ICON_NAMES_BY_ACTION)
            .to include(metadata.action)
        end
      end
    end

    context 'it succeeds' do
      where(:action, :icon, :human_description) do
        [
          [:creation,     'designs_added',    'added'],
          [:modification, 'designs_modified', 'updated'],
          [:deletion,     'designs_removed',  'removed']
        ]
      end

      with_them do
        before do
          version.actions.update_all(event: action)
        end

        let(:anchor_tag) { %r{ <a[^>]*>#{link}</a>} }
        let(:href) { instance.send(:designs_path, { version: version.id }) }
        let(:link) { "#{n_designs} designs" }

        subject(:note) { instance.design_version_added(version).first }

        it 'has the correct data' do
          expect(note)
            .to be_system
            .and have_attributes(
              system_note_metadata: have_attributes(action: icon),
              note: include(human_description)
                      .and(include link)
                      .and(include href),
              note_html: a_string_matching(anchor_tag)
            )
        end
      end
    end
  end

  describe '#design_discussion_added' do
    let(:instance_noteable) { design.issue }
    let(:instance_project) { design.issue.project }
    let(:instance_author) { discussion_note.author }

    subject { instance.design_discussion_added(discussion_note) }

    let(:design) { create(:design, :with_file, issue: issue) }
    let(:author) { create(:user) }
    let(:discussion_note) do
      create(:diff_note_on_design, noteable: design, author: author)
    end

    let(:action) { 'designs_discussion_added' }

    it_behaves_like 'a system note' do
      let(:noteable) { discussion_note.noteable.issue }
    end

    it 'adds a new system note' do
      expect { subject }.to change { Note.system.count }.by(1)
    end

    it 'has the correct note text' do
      href = instance.send(:designs_path,
        { vueroute: design.filename, anchor: ActionView::RecordIdentifier.dom_id(discussion_note) }
      )

      expect(subject.note).to eq("started a discussion on [#{design.filename}](#{href})")
    end
  end
end


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

module SystemNotes
  class IncidentsService < ::SystemNotes::BaseService
    CHANGED_TEXT = {
      occurred_at: 'the event time/date on ',
      note: 'the text on ',
      occurred_at_and_note: 'the event time/date and text on '
    }.freeze

    def initialize(noteable:)
      @noteable = noteable
      @project = noteable.project
    end

    def add_timeline_event(timeline_event)
      author = timeline_event.author
      body = 'added an incident timeline event'

      create_note(NoteSummary.new(noteable, project, author, body, action: 'timeline_event'))
    end

    def edit_timeline_event(timeline_event, author, was_changed:)
      changed_text = CHANGED_TEXT.fetch(was_changed, '')
      body = "edited #{changed_text}incident timeline event"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'timeline_event'))
    end

    def delete_timeline_event(author)
      body = 'deleted an incident timeline event'

      create_note(NoteSummary.new(noteable, project, author, body, action: 'timeline_event'))
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe SystemNotes::IncidentsService, feature_category: :incident_management do
  let_it_be(:project) { create(:project) }
  let_it_be(:user) { create(:user) }
  let_it_be(:author) { create(:user) }
  let_it_be(:incident) { create(:incident, project: project, author: user) }
  let_it_be(:timeline_event) do
    create(:incident_management_timeline_event, project: project, incident: incident, author: author)
  end

  describe '#add_timeline_event' do
    subject { described_class.new(noteable: incident).add_timeline_event(timeline_event) }

    it_behaves_like 'a system note' do
      let(:noteable) { incident }
      let(:action) { 'timeline_event' }
    end

    it 'posts the correct text to the system note' do
      expect(subject.note).to match("added an incident timeline event")
    end
  end

  describe '#edit_timeline_event' do
    let(:was_changed) { :unknown }

    subject do
      described_class.new(noteable: incident).edit_timeline_event(timeline_event, author, was_changed: was_changed)
    end

    it_behaves_like 'a system note' do
      let(:noteable) { incident }
      let(:action) { 'timeline_event' }
    end

    context "when only timeline event's occurred_at was changed" do
      let(:was_changed) { :occurred_at }

      it 'posts the correct text to the system note' do
        expect(subject.note).to match("edited the event time/date on incident timeline event")
      end
    end

    context "when only timeline event's note was changed" do
      let(:was_changed) { :note }

      it 'posts the correct text to the system note' do
        expect(subject.note).to match("edited the text on incident timeline event")
      end
    end

    context "when both timeline events occurred_at and note was changed" do
      let(:was_changed) { :occurred_at_and_note }

      it 'posts the correct text to the system note' do
        expect(subject.note).to match("edited the event time/date and text on incident timeline event")
      end
    end

    context "when was changed reason is unknown" do
      let(:was_changed) { :unknown }

      it 'posts the correct text to the system note' do
        expect(subject.note).to match("edited incident timeline event")
      end
    end
  end

  describe '#delete_timeline_event' do
    subject { described_class.new(noteable: incident).delete_timeline_event(author) }

    it_behaves_like 'a system note' do
      let(:noteable) { incident }
      let(:action) { 'timeline_event' }
    end

    it 'posts the correct text to the system note' do
      expect(subject.note).to match('deleted an incident timeline event')
    end
  end
end


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

module SystemNotes
  class CommitService < ::SystemNotes::BaseService
    NEW_COMMIT_DISPLAY_LIMIT = 10

    # Called when commits are added to a merge request
    #
    # new_commits      - Array of Commits added since last push
    # existing_commits - Array of Commits added in a previous push
    # oldrev           - Optional String SHA of a previous Commit
    #
    # See new_commit_summary and existing_commit_summary.
    #
    # Returns the created Note object
    def add_commits(new_commits, existing_commits = [], oldrev = nil)
      total_count  = new_commits.length + existing_commits.length
      commits_text = "#{total_count} commit".pluralize(total_count)

      text_parts = ["added #{commits_text}"]
      text_parts << commits_list(noteable, new_commits, existing_commits, oldrev)
      text_parts << "[Compare with previous version](#{diff_comparison_path(noteable, project, oldrev)})"

      body = text_parts.join("\n\n")

      create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count))
    end

    # Called when a commit was tagged
    #
    # tag_name  - The created tag name
    #
    # Returns the created Note object
    def tag_commit(tag_name)
      link = url_helpers.project_tag_path(project, id: tag_name)
      body = "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'tag'))
    end

    private

    # Build an Array of lines detailing each commit added in a merge request
    #
    # new_commits - Array of new Commit objects
    #
    # Returns an Array of Strings
    def new_commits_list(new_commits)
      new_commits.collect do |commit|
        content_tag('li', "#{commit.short_id} - #{commit.title}")
      end
    end

    # Builds an Array of lines describing each commit and truncate them based on the limit
    # to avoid creating a note with a large number of commits.
    #
    # commits - Array of Commit objects
    #
    # Returns an Array of Strings
    #
    # rubocop: disable CodeReuse/ActiveRecord
    def new_commit_summary(commits, start_rev)
      if commits.size > NEW_COMMIT_DISPLAY_LIMIT
        no_of_commits_to_truncate = commits.size - NEW_COMMIT_DISPLAY_LIMIT
        commits_to_truncate = commits.take(no_of_commits_to_truncate)
        remaining_commits = commits.drop(no_of_commits_to_truncate)

        [truncated_new_commits(commits_to_truncate, start_rev)] + new_commits_list(remaining_commits)
      else
        new_commits_list(commits)
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    # Builds a summary line that describes given truncated commits.
    #
    # commits - Array of Commit objects
    # start_rev - String SHA of a Commit that will be used as the starting SHA of the range
    #
    # Returns a String wrapped in 'li' tag.
    def truncated_new_commits(commits, start_rev)
      count = commits.size

      commit_ids = if count == 1
                     commits.first.short_id
                   elsif start_rev && !Gitlab::Git.blank_ref?(start_rev)
                     "#{Commit.truncate_sha(start_rev)}...#{commits.last.short_id}"
                   else
                     # This two-dots notation seems to be not functioning as expected, but we should
                     # fallback to it as start_rev can be empty.
                     #
                     # For more information, please see https://gitlab.com/gitlab-org/gitlab/-/issues/391809
                     "#{commits.first.short_id}..#{commits.last.short_id}"
                   end

      commits_text = "#{count} earlier commit".pluralize(count)

      content_tag('li', "#{commit_ids} - #{commits_text}")
    end

    # Builds a list of existing and new commits according to existing_commits and
    # new_commits methods.
    # Returns a String wrapped in `ul` and `li` tags.
    def commits_list(noteable, new_commits, existing_commits, oldrev)
      existing_commit_summary = existing_commit_summary(noteable, existing_commits, oldrev)
      start_rev = existing_commits.empty? ? oldrev : existing_commits.last.id
      new_commit_summary = new_commit_summary(new_commits, start_rev).join

      content_tag('ul', "#{existing_commit_summary}#{new_commit_summary}".html_safe)
    end

    # Build a single line summarizing existing commits being added in a merge
    # request
    #
    # existing_commits - Array of existing Commit objects
    # oldrev           - Optional String SHA of a previous Commit
    #
    # Examples:
    #
    #   "* ea0f8418...2f4426b7 - 24 commits from branch `master`"
    #
    #   "* ea0f8418..4188f0ea - 15 commits from branch `fork:master`"
    #
    #   "* ea0f8418 - 1 commit from branch `feature`"
    #
    # Returns a newline-terminated String
    def existing_commit_summary(noteable, existing_commits, oldrev = nil)
      return '' if existing_commits.empty?

      count = existing_commits.size

      commit_ids = if count == 1
                     existing_commits.first.short_id
                   elsif oldrev && !Gitlab::Git.blank_ref?(oldrev)
                     "#{Commit.truncate_sha(oldrev)}...#{existing_commits.last.short_id}"
                   else
                     "#{existing_commits.first.short_id}..#{existing_commits.last.short_id}"
                   end

      commits_text = "#{count} commit".pluralize(count)

      branch = noteable.target_branch
      branch = "#{noteable.target_project_namespace}:#{branch}" if noteable.for_fork?

      branch_name = content_tag('code', branch)
      content_tag('li', "#{commit_ids} - #{commits_text} from branch #{branch_name}".html_safe)
    end

    def diff_comparison_path(merge_request, project, oldrev)
      diff_id = merge_request.merge_request_diff.id

      url_helpers.diffs_project_merge_request_path(
        project,
        merge_request,
        diff_id: diff_id,
        start_sha: oldrev
      )
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe SystemNotes::CommitService, feature_category: :code_review_workflow do
  let_it_be(:group)    { create(:group) }
  let_it_be(:project)  { create(:project, :repository, group: group) }
  let_it_be(:author)   { create(:user) }

  let(:commit_service) { described_class.new(noteable: noteable, project: project, author: author) }

  describe '#add_commits' do
    subject { commit_service.add_commits(new_commits, old_commits, oldrev) }

    let(:noteable)    { create(:merge_request, source_project: project, target_project: project) }
    let(:new_commits) { create_commits(10) }
    let(:old_commits) { [] }
    let(:oldrev)      { nil }

    it_behaves_like 'a system note' do
      let(:commit_count) { new_commits.size }
      let(:action)       { 'commit' }
    end

    describe 'note body' do
      let(:note_lines) { subject.note.split("\n").reject(&:blank?) }

      describe 'comparison diff link line' do
        it 'adds the comparison text' do
          expect(note_lines[2]).to match "[Compare with previous version]"
        end
      end

      context 'without existing commits' do
        it 'adds a message header' do
          expect(note_lines[0]).to eq "added #{new_commits.size} commits"
        end

        it 'adds a message for each commit' do
          decoded_note_content = HTMLEntities.new.decode(subject.note)

          new_commits.each do |commit|
            expect(decoded_note_content).to include("
#{commit.short_id} - #{commit.title}
")
          end
        end

        context 'with HTML content' do
          let(:new_commits) { [double(title: '
This is a test
', short_id: '12345678')] }

          it 'escapes HTML titles' do
            expect(note_lines[1]).to eq("

12345678 - <pre>This is a test</pre>

")
          end
        end

        context 'with one commit exceeding the NEW_COMMIT_DISPLAY_LIMIT' do
          let(:new_commits) { create_commits(11) }
          let(:earlier_commit_summary_line) { note_lines[1] }

          it 'includes the truncated new commits summary' do
            expect(earlier_commit_summary_line).to start_with("

#{new_commits[0].short_id} - 1 earlier commit")
          end

          context 'with oldrev' do
            let(:oldrev) { '12345678abcd' }

            it 'includes the truncated new commits summary with the oldrev' do
              expect(earlier_commit_summary_line).to start_with("

#{new_commits[0].short_id} - 1 earlier commit")
            end
          end
        end

        context 'with multiple commits exceeding the NEW_COMMIT_DISPLAY_LIMIT' do
          let(:new_commits) { create_commits(13) }
          let(:earlier_commit_summary_line) { note_lines[1] }

          it 'includes the truncated new commits summary' do
            expect(earlier_commit_summary_line).to start_with("

#{new_commits[0].short_id}..#{new_commits[2].short_id} - 3 earlier commits")
          end

          context 'with oldrev' do
            let(:oldrev) { '12345678abcd' }

            it 'includes the truncated new commits summary with the oldrev' do
              expect(earlier_commit_summary_line).to start_with("

12345678...#{new_commits[2].short_id} - 3 earlier commits")
            end
          end
        end
      end

      describe 'summary line for existing commits' do
        let(:summary_line) { note_lines[1] }

        context 'with one existing commit' do
          let(:old_commits) { [noteable.commits.last] }

          it 'includes the existing commit' do
            expect(summary_line).to start_with("

#{old_commits.first.short_id} - 1 commit from branch feature")
          end

          context 'with new commits exceeding the display limit' do
            let(:summary_line) { note_lines[1] }
            let(:new_commits) { create_commits(13) }

            it 'includes the existing commit as well as the truncated new commit summary' do
              expect(summary_line).to start_with("

#{old_commits.first.short_id} - 1 commit from branch feature

#{old_commits.last.short_id}...#{new_commits[2].short_id} - 3 earlier commits")
            end
          end
        end

        context 'with multiple existing commits' do
          let(:old_commits) { noteable.commits[3..] }

          context 'with oldrev' do
            let(:oldrev) { noteable.commits[2].id }

            it 'includes a commit range and count' do
              expect(summary_line)
                .to start_with("

#{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id} - 26 commits from branch feature")
            end

            context 'with new commits exceeding the display limit' do
              let(:new_commits) { create_commits(13) }

              it 'includes the existing commit as well as the truncated new commit summary' do
                expect(summary_line)
                  .to start_with("

#{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id} - 26 commits from branch feature

#{old_commits.last.short_id}...#{new_commits[2].short_id} - 3 earlier commits")
              end
            end
          end

          context 'without oldrev' do
            it 'includes a commit range and count' do
              expect(summary_line)
                .to start_with("

#{old_commits[0].short_id}..#{old_commits[-1].short_id} - 26 commits from branch feature")
            end

            context 'with new commits exceeding the display limit' do
              let(:new_commits) { create_commits(13) }

              it 'includes the existing commit as well as the truncated new commit summary' do
                expect(summary_line)
                  .to start_with("

#{old_commits.first.short_id}..#{old_commits.last.short_id} - 26 commits from branch feature

#{old_commits.last.short_id}...#{new_commits[2].short_id} - 3 earlier commits")
              end
            end
          end

          context 'on a fork' do
            before do
              expect(noteable).to receive(:for_fork?).and_return(true)
            end

            it 'includes the project namespace' do
              expect(summary_line).to include("#{noteable.target_project_namespace}:feature")
            end
          end
        end
      end
    end
  end

  describe '#tag_commit' do
    let(:noteable) { project.commit }
    let(:tag_name) { 'v1.2.3' }

    subject { commit_service.tag_commit(tag_name) }

    it_behaves_like 'a system note' do
      let(:action) { 'tag' }
    end

    it 'sets the note text' do
      link = "/#{project.full_path}/-/tags/#{tag_name}"

      expect(subject.note).to eq "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"
    end
  end

  def create_commits(count)
    Array.new(count) do |i|
      double(title: "Test commit #{i}", short_id: "abcd00#{i}")
    end
  end
end


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

module SystemNotes
  class AlertManagementService < ::SystemNotes::BaseService
    # Called when the a new AlertManagement::Alert has been created
    #
    # alert - AlertManagement::Alert object.
    #
    # Example Note text:
    #
    #   "GitLab Alert Bot logged an alert from Prometheus"
    #
    # Returns the created Note object
    def create_new_alert(monitoring_tool)
      body = "logged an alert from **#{monitoring_tool}**"

      create_note(NoteSummary.new(noteable, project, Users::Internal.alert_bot, body, action: 'new_alert_added'))
    end

    # Called when the status of an AlertManagement::Alert has changed
    #
    # alert - AlertManagement::Alert object.
    #
    # Example Note text:
    #
    #   "changed the status to Acknowledged"
    #   "changed the status to Acknowledged by changing the incident status of #540"
    #
    # Returns the created Note object
    def change_alert_status(reason)
      status = noteable.state.to_s.titleize
      body = "changed the status to **#{status}**#{reason}"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'status'))
    end

    # Called when an issue is created based on an AlertManagement::Alert
    #
    # issue - Issue object.
    #
    # Example Note text:
    #
    #   "created incident #17 for this alert"
    #
    # Returns the created Note object
    def new_alert_issue(issue)
      body = "created incident #{issue.to_reference(project)} for this alert"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'alert_issue_added'))
    end

    # Called when an alert is resolved due to received resolving alert payload
    #
    # alert - AlertManagement::Alert object.
    #
    # Example Note text:
    #
    #   "changed the status to Resolved by closing issue #17"
    #
    # Returns the created Note object
    def log_resolving_alert(monitoring_tool)
      body = "logged a recovery alert from **#{monitoring_tool}**"

      create_note(NoteSummary.new(noteable, project, Users::Internal.alert_bot, body, action: 'new_alert_added'))
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::SystemNotes::AlertManagementService, feature_category: :groups_and_projects do
  let_it_be(:author)   { create(:user) }
  let_it_be(:project)  { create(:project, :repository) }
  let_it_be(:noteable) { create(:alert_management_alert, :with_incident, :acknowledged, project: project) }

  describe '#create_new_alert' do
    subject { described_class.new(noteable: noteable, project: project).create_new_alert('Some Service') }

    it_behaves_like 'a system note' do
      let(:author) { Users::Internal.alert_bot }
      let(:action) { 'new_alert_added' }
    end

    it 'has the appropriate message' do
      expect(subject.note).to eq('logged an alert from **Some Service**')
    end
  end

  describe '#change_alert_status' do
    subject { described_class.new(noteable: noteable, project: project, author: author).change_alert_status(reason) }

    context 'with no specified reason' do
      let(:reason) { nil }

      it_behaves_like 'a system note' do
        let(:action) { 'status' }
      end

      it 'has the appropriate message' do
        expect(subject.note).to eq("changed the status to **Acknowledged**")
      end
    end

    context 'with reason provided' do
      let(:reason) { ' by changing incident status' }

      it 'has the appropriate message' do
        expect(subject.note).to eq("changed the status to **Acknowledged** by changing incident status")
      end
    end
  end

  describe '#new_alert_issue' do
    let_it_be(:issue) { noteable.issue }

    subject { described_class.new(noteable: noteable, project: project, author: author).new_alert_issue(issue) }

    it_behaves_like 'a system note' do
      let(:action) { 'alert_issue_added' }
    end

    it 'has the appropriate message' do
      expect(subject.note).to eq("created incident #{issue.to_reference(project)} for this alert")
    end
  end

  describe '#log_resolving_alert' do
    subject { described_class.new(noteable: noteable, project: project).log_resolving_alert('Some Service') }

    it_behaves_like 'a system note' do
      let(:author) { Users::Internal.alert_bot }
      let(:action) { 'new_alert_added' }
    end

    it 'has the appropriate message' do
      expect(subject.note).to eq('logged a recovery alert from **Some Service**')
    end
  end
end


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

module SystemNotes
  class MergeRequestsService < ::SystemNotes::BaseService
    # Called when 'merge when pipeline succeeds' is executed
    def merge_when_pipeline_succeeds(sha)
      body = "enabled an automatic merge when the pipeline for #{sha} succeeds"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
    end

    # Called when 'merge when pipeline succeeds' is canceled
    def cancel_merge_when_pipeline_succeeds
      body = 'canceled the automatic merge'

      create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
    end

    # Called when 'merge when pipeline succeeds' is aborted
    def abort_merge_when_pipeline_succeeds(reason)
      body = "aborted the automatic merge because #{reason}"

      ##
      # TODO: Abort message should be sent by the system, not a particular user.
      # See https://gitlab.com/gitlab-org/gitlab-foss/issues/63187.
      create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
    end

    def handle_merge_request_draft
      action = noteable.draft? ? "draft" : "ready"

      body = "marked this merge request as **#{action}**"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
    end

    def add_merge_request_draft_from_commit(commit)
      body = "marked this merge request as **draft** from #{commit.to_reference(project)}"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
    end

    def resolve_all_discussions
      body = "resolved all threads"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'discussion'))
    end

    def discussion_continued_in_issue(discussion, issue)
      body = "created #{issue.to_reference} to continue this discussion"
      note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)

      Note.create(note_attributes.merge(system: true, created_at: issue.system_note_timestamp)).tap do |note|
        note.system_note_metadata = SystemNoteMetadata.new(action: 'discussion')
      end
    end

    def diff_discussion_outdated(discussion, change_position)
      merge_request = discussion.noteable
      diff_refs = change_position.diff_refs
      version_index = merge_request.merge_request_diffs.viewable.count
      position_on_text = change_position.on_text?
      text_parts = ["changed this #{position_on_text ? 'line' : 'file'} in"]

      if version_params = merge_request.version_params_for(diff_refs)
        repository = project.repository
        anchor = position_on_text ? change_position.line_code(repository) : change_position.file_hash
        url = url_helpers.diffs_project_merge_request_path(project, merge_request, version_params.merge(anchor: anchor))

        text_parts << "[version #{version_index} of the diff](#{url})"
      else
        text_parts << "version #{version_index} of the diff"
      end

      body = text_parts.join(' ')
      note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)

      Note.create(note_attributes.merge(system: true)).tap do |note|
        note.system_note_metadata = SystemNoteMetadata.new(action: 'outdated')
      end
    end

    # Called when a branch in Noteable is changed
    #
    # branch_type - 'source' or 'target'
    # event_type  - the source of event: 'update' or 'delete'
    # old_branch  - old branch name
    # new_branch  - new branch name

    # Example Note text is based on event_type:
    #
    #   update: "changed target branch from `Old` to `New`"
    #   delete: "deleted the `Old` branch. This merge request now targets the `New` branch"
    #
    # Returns the created Note object
    def change_branch(branch_type, event_type, old_branch, new_branch)
      body =
        case event_type.to_s
        when 'delete'
          "deleted the `#{old_branch}` branch. This merge request now targets the `#{new_branch}` branch"
        when 'update'
          "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`"
        else
          raise ArgumentError, "invalid value for event_type: #{event_type}"
        end

      create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
    end

    # Called when a branch in Noteable is added or deleted
    #
    # branch_type - :source or :target
    # branch      - branch name
    # presence    - :add or :delete
    #
    # Example Note text:
    #
    #   "restored target branch `feature`"
    #
    # Returns the created Note object
    def change_branch_presence(branch_type, branch, presence)
      verb =
        if presence == :add
          'restored'
        else
          'deleted'
        end

      body = "#{verb} #{branch_type} branch `#{branch}`"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
    end

    # Called when a branch is created from the 'new branch' button on a issue
    # Example note text:
    #
    #   "created branch `201-issue-branch-button`"
    def new_issue_branch(branch, branch_project: nil)
      branch_project ||= project
      link = url_helpers.project_compare_path(branch_project, from: branch_project.default_branch, to: branch)

      body = "created branch [`#{branch}`](#{link}) to address this issue"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
    end

    def new_merge_request(merge_request)
      body = "created merge request #{merge_request.to_reference(project)} to address this issue"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
    end

    def picked_into_branch(branch_name, pick_commit)
      link = url_helpers.project_tree_path(project, branch_name)

      body = "picked the changes into the branch [`#{branch_name}`](#{link}) with commit #{pick_commit}"

      summary = NoteSummary.new(noteable, project, author, body, action: 'cherry_pick')
      summary.note[:commit_id] = pick_commit

      create_note(summary)
    end

    # Called when the merge request is approved by user
    #
    # Example Note text:
    #
    #   "approved this merge request"
    #
    # Returns the created Note object
    def approve_mr
      body = "approved this merge request"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'approved'))
    end

    def unapprove_mr
      body = "unapproved this merge request"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'unapproved'))
    end
  end
end

SystemNotes::MergeRequestsService.prepend_mod_with('SystemNotes::MergeRequestsService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::SystemNotes::MergeRequestsService, feature_category: :code_review_workflow do
  include Gitlab::Routing

  let_it_be(:group) { create(:group) }
  let_it_be(:project) { create(:project, :repository, group: group) }
  let_it_be(:author) { create(:user) }

  let(:noteable) { create(:merge_request, source_project: project, target_project: project) }

  let(:service) { described_class.new(noteable: noteable, project: project, author: author) }

  describe '.merge_when_pipeline_succeeds' do
    let(:pipeline) { build(:ci_pipeline) }

    subject { service.merge_when_pipeline_succeeds(pipeline.sha) }

    it_behaves_like 'a system note' do
      let(:action) { 'merge' }
    end

    it "posts the 'merge when pipeline succeeds' system note" do
      expect(subject.note).to match(%r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{40} succeeds})
    end
  end

  describe '.cancel_merge_when_pipeline_succeeds' do
    subject { service.cancel_merge_when_pipeline_succeeds }

    it_behaves_like 'a system note' do
      let(:action) { 'merge' }
    end

    it "posts the 'merge when pipeline succeeds' system note" do
      expect(subject.note).to eq "canceled the automatic merge"
    end
  end

  describe '.abort_merge_when_pipeline_succeeds' do
    subject { service.abort_merge_when_pipeline_succeeds('merge request was closed') }

    it_behaves_like 'a system note' do
      let(:action) { 'merge' }
    end

    it "posts the 'merge when pipeline succeeds' system note" do
      expect(subject.note).to eq "aborted the automatic merge because merge request was closed"
    end
  end

  describe '.handle_merge_request_draft' do
    context 'adding draft note' do
      let(:noteable) { create(:merge_request, source_project: project, title: 'Draft: Lorem ipsum') }

      subject { service.handle_merge_request_draft }

      it_behaves_like 'a system note' do
        let(:action) { 'title' }
      end

      it 'sets the note text' do
        expect(subject.note).to eq 'marked this merge request as **draft**'
      end
    end

    context 'removing draft note' do
      subject { service.handle_merge_request_draft }

      it_behaves_like 'a system note' do
        let(:action) { 'title' }
      end

      it 'sets the note text' do
        expect(subject.note).to eq 'marked this merge request as **ready**'
      end
    end
  end

  describe '.add_merge_request_draft_from_commit' do
    subject { service.add_merge_request_draft_from_commit(noteable.diff_head_commit) }

    it_behaves_like 'a system note' do
      let(:action) { 'title' }
    end

    it "posts the 'marked this merge request as draft from commit' system note" do
      expect(subject.note).to match(
        /marked this merge request as \*\*draft\*\* from #{Commit.reference_pattern}/
      )
    end
  end

  describe '.resolve_all_discussions' do
    subject { service.resolve_all_discussions }

    it_behaves_like 'a system note' do
      let(:action) { 'discussion' }
    end

    it 'sets the note text' do
      expect(subject.note).to eq 'resolved all threads'
    end
  end

  describe '.diff_discussion_outdated' do
    let(:discussion) { create(:diff_note_on_merge_request, project: project).to_discussion }
    let(:merge_request) { discussion.noteable }
    let(:change_position) { discussion.position }

    def reloaded_merge_request
      MergeRequest.find(merge_request.id)
    end

    let(:service) { described_class.new(project: project, author: author) }

    subject { service.diff_discussion_outdated(discussion, change_position) }

    it_behaves_like 'a system note' do
      let(:expected_noteable) { discussion.first_note.noteable }
      let(:action)            { 'outdated' }
    end

    context 'when the change_position is valid for the discussion' do
      it 'creates a new note in the discussion' do
        # we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded.
        expect { subject }.to change { reloaded_merge_request.discussions.first.notes.size }.by(1)
      end

      it 'links to the diff in the system note' do
        diff_id = merge_request.merge_request_diff.id
        line_code = change_position.line_code(project.repository)
        link = diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: line_code)

        expect(subject.note).to eq("changed this line in [version 1 of the diff](#{link})")
      end

      context 'discussion is on an image' do
        let(:discussion) { create(:image_diff_note_on_merge_request, project: project).to_discussion }

        it 'links to the diff in the system note' do
          diff_id = merge_request.merge_request_diff.id
          file_hash = change_position.file_hash
          link = diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: file_hash)

          expect(subject.note).to eq("changed this file in [version 1 of the diff](#{link})")
        end
      end
    end

    context 'when the change_position does not point to a valid version' do
      before do
        allow(merge_request).to receive(:version_params_for).and_return(nil)
      end

      it 'creates a new note in the discussion' do
        # we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded.
        expect { subject }.to change { reloaded_merge_request.discussions.first.notes.size }.by(1)
      end

      it 'does not create a link' do
        expect(subject.note).to eq('changed this line in version 1 of the diff')
      end
    end
  end

  describe '.change_branch' do
    let(:old_branch) { 'old_branch' }
    let(:new_branch) { 'new_branch' }

    it_behaves_like 'a system note' do
      let(:action) { 'branch' }

      subject { service.change_branch('target', 'update', old_branch, new_branch) }
    end

    context 'when target branch name changed' do
      context 'on update' do
        subject { service.change_branch('target', 'update', old_branch, new_branch) }

        it 'sets the note text' do
          expect(subject.note).to eq "changed target branch from `#{old_branch}` to `#{new_branch}`"
        end
      end

      context 'on delete' do
        subject { service.change_branch('target', 'delete', old_branch, new_branch) }

        it 'sets the note text' do
          expect(subject.note).to eq "deleted the `#{old_branch}` branch. This merge request now targets the `#{new_branch}` branch"
        end
      end

      context 'for invalid event_type' do
        subject { service.change_branch('target', 'invalid', old_branch, new_branch) }

        it 'raises exception' do
          expect { subject }.to raise_error /invalid value for event_type/
        end
      end
    end
  end

  describe '.change_branch_presence' do
    subject { service.change_branch_presence(:source, 'feature', :delete) }

    it_behaves_like 'a system note' do
      let(:action) { 'branch' }
    end

    context 'when source branch deleted' do
      it 'sets the note text' do
        expect(subject.note).to eq "deleted source branch `feature`"
      end
    end
  end

  describe '.new_issue_branch' do
    let(:branch) { '1-mepmep' }

    subject { service.new_issue_branch(branch, branch_project: branch_project) }

    shared_examples_for 'a system note for new issue branch' do
      it_behaves_like 'a system note' do
        let(:action) { 'branch' }
      end

      context 'when a branch is created from the new branch button' do
        it 'sets the note text' do
          expect(subject.note).to start_with("created branch [`#{branch}`]")
        end
      end
    end

    context 'branch_project is set' do
      let(:branch_project) { create(:project, :repository) }

      it_behaves_like 'a system note for new issue branch'
    end

    context 'branch_project is not set' do
      let(:branch_project) { nil }

      it_behaves_like 'a system note for new issue branch'
    end
  end

  describe '.new_merge_request' do
    subject { service.new_merge_request(merge_request) }

    let!(:merge_request) { create(:merge_request, source_project: project, source_branch: generate(:branch), target_project: project) }

    it_behaves_like 'a system note' do
      let(:action) { 'merge' }
    end

    it 'sets the new merge request note text' do
      expect(subject.note).to eq("created merge request #{merge_request.to_reference(project)} to address this issue")
    end
  end

  describe '.picked_into_branch' do
    let(:branch_name) { 'a-branch' }
    let(:commit_sha) { project.commit.sha }
    let(:merge_request) { noteable }

    subject { service.picked_into_branch(branch_name, commit_sha) }

    it_behaves_like 'a system note' do
      let(:action) { 'cherry_pick' }
    end

    it "posts the 'picked merge request' system note" do
      expect(subject.note).to eq("picked the changes into the branch [`#{branch_name}`](/#{project.full_path}/-/tree/#{branch_name}) with commit #{commit_sha}")
    end

    it 'links the merge request and the cherry-pick commit' do
      expect(subject.noteable).to eq(merge_request)
      expect(subject.commit_id).to eq(commit_sha)
    end
  end

  describe '#approve_mr' do
    subject { described_class.new(noteable: noteable, project: project, author: author).approve_mr }

    it_behaves_like 'a system note' do
      let(:action) { 'approved' }
    end

    context 'when merge request approved' do
      it 'sets the note text' do
        expect(subject.note).to eq "approved this merge request"
      end
    end
  end
end


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

module SystemNotes
  class BaseService
    attr_reader :noteable, :project, :author

    def initialize(noteable: nil, author: nil, project: nil)
      @noteable = noteable
      @project = project
      @author = author
    end

    protected

    def create_note(note_summary)
      note_params = note_summary.note.merge(system: true)
      note_params[:system_note_metadata] = SystemNoteMetadata.new(note_summary.metadata) if note_summary.metadata?

      Note.create(note_params)
    end

    def content_tag(...)
      ActionController::Base.helpers.content_tag(...)
    end

    def url_helpers
      @url_helpers ||= Gitlab::Routing.url_helpers
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe SystemNotes::BaseService, feature_category: :groups_and_projects do
  let(:noteable) { double }
  let(:project) { double }
  let(:author) { double }

  let(:base_service) { described_class.new(noteable: noteable, project: project, author: author) }

  describe '#noteable' do
    subject { base_service.noteable }

    it { is_expected.to eq(noteable) }

    it 'returns nil if no arguments are given' do
      instance = described_class.new
      expect(instance.noteable).to be_nil
    end
  end

  describe '#project' do
    subject { base_service.project }

    it { is_expected.to eq(project) }

    it 'returns nil if no arguments are given' do
      instance = described_class.new
      expect(instance.project).to be_nil
    end
  end

  describe '#author' do
    subject { base_service.author }

    it { is_expected.to eq(author) }

    it 'returns nil if no arguments are given' do
      instance = described_class.new
      expect(instance.author).to be_nil
    end
  end
end


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

module SystemNotes
  class TimeTrackingService < ::SystemNotes::BaseService
    # Called when the start_date or due_date of an Issue/WorkItem is changed
    #
    # start_date  - Start date being assigned, or nil
    # due_date  - Due date being assigned, or nil
    #
    # Example Note text:
    #
    #   "removed due date"
    #
    #   "changed due date to September 20, 2018"

    #   "changed start date to September 20, 2018 and changed due date to September 25, 2018"
    #
    # Returns the created Note object
    def change_start_date_or_due_date(changed_dates = {})
      return if changed_dates.empty?

      # Using instance_of because WorkItem < Issue. We don't want to track work item updates as issue updates
      if noteable.instance_of?(Issue) && changed_dates.key?('due_date')
        issue_activity_counter.track_issue_due_date_changed_action(author: author, project: project)
      end

      work_item_activity_counter.track_work_item_date_changed_action(author: author) if noteable.is_a?(WorkItem)

      create_note(
        NoteSummary.new(noteable, project, author, changed_date_body(changed_dates), action: 'start_date_or_due_date')
      )
    end

    # Called when the estimated time of a Noteable is changed
    #
    # time_estimate - Estimated time
    #
    # Example Note text:
    #
    #   "removed time estimate"
    #
    #   "changed time estimate to 3d 5h"
    #
    # Returns the created Note object
    def change_time_estimate
      parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate)
      body = if noteable.time_estimate == 0
               "removed time estimate"
             else
               "changed time estimate to #{parsed_time}"
             end

      if noteable.is_a?(Issue)
        issue_activity_counter.track_issue_time_estimate_changed_action(author: author, project: project)
      end

      create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
    end

    # Called when the spent time of a Noteable is changed
    #
    # time_spent - Spent time
    #
    # Example Note text:
    #
    #   "removed time spent"
    #
    #   "added 2h 30m of time spent"
    #
    # Returns the created Note object
    def change_time_spent
      time_spent = noteable.time_spent

      if time_spent == :reset
        body = "removed time spent"
      else
        spent_at = noteable.spent_at&.to_date
        parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs)
        action = time_spent > 0 ? 'added' : 'subtracted'

        text_parts = ["#{action} #{parsed_time} of time spent"]
        text_parts << "at #{spent_at}" if spent_at && spent_at != DateTime.current.to_date
        body = text_parts.join(' ')
      end

      if noteable.is_a?(Issue)
        issue_activity_counter.track_issue_time_spent_changed_action(author: author, project: project)
      end

      create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
    end

    # Called when a timelog is added to an issuable
    #
    # timelog - Added timelog
    #
    # Example Note text:
    #
    #   "subtracted 1h 15m of time spent"
    #
    #   "added 2h 30m of time spent"
    #
    # Returns the created Note object
    def created_timelog(timelog)
      time_spent = timelog.time_spent
      spent_at = timelog.spent_at&.to_date
      parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs)
      action = time_spent > 0 ? 'added' : 'subtracted'

      text_parts = ["#{action} #{parsed_time} of time spent"]
      text_parts << "at #{spent_at}" if spent_at && spent_at != DateTime.current.to_date
      body = text_parts.join(' ')

      if noteable.is_a?(Issue)
        issue_activity_counter.track_issue_time_spent_changed_action(author: author, project: project)
      end

      create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
    end

    def remove_timelog(timelog)
      time_spent = timelog.time_spent
      spent_at = timelog.spent_at&.to_date

      parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent)

      body = "deleted #{parsed_time} of spent time"
      body += " from #{spent_at}" if spent_at

      create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking'))
    end

    private

    def changed_date_body(changed_dates)
      %w[start_date due_date].each_with_object([]) do |date_field, word_array|
        next unless changed_dates.key?(date_field)

        word_array << 'and' if word_array.any?

        word_array << message_for_changed_date(changed_dates, date_field)
      end.join(' ')
    end

    def message_for_changed_date(changed_dates, date_key)
      changed_date = changed_dates[date_key].last
      readable_date = date_key.humanize.downcase

      if changed_date.nil?
        "removed #{readable_date} #{changed_dates[date_key].first.to_fs(:long)}"
      else
        "changed #{readable_date} to #{changed_date.to_fs(:long)}"
      end
    end

    def issue_activity_counter
      Gitlab::UsageDataCounters::IssueActivityUniqueCounter
    end

    def work_item_activity_counter
      Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_planning do
  let_it_be(:author)  { create(:user) }
  let_it_be(:project) { create(:project, :repository) }

  describe '#change_start_date_or_due_date' do
    let_it_be(:issue)     { create(:issue, project: project) }
    let_it_be(:work_item) { create(:work_item, project: project) }

    subject(:note) { described_class.new(noteable: noteable, project: project, author: author).change_start_date_or_due_date(changed_dates) }

    let(:start_date) { Date.today }
    let(:due_date) { 1.week.from_now.to_date }
    let(:changed_dates) { { 'due_date' => [nil, due_date], 'start_date' => [nil, start_date] } }

    shared_examples 'issuable getting date change notes' do
      it_behaves_like 'a note with overridable created_at'

      it_behaves_like 'a system note' do
        let(:action) { 'start_date_or_due_date' }
      end

      context 'when both dates are added' do
        it 'sets the correct note message' do
          expect(note.note).to eq("changed start date to #{start_date.to_fs(:long)} and changed due date to #{due_date.to_fs(:long)}")
        end
      end

      context 'when both dates are removed' do
        let(:changed_dates) { { 'due_date' => [due_date, nil], 'start_date' => [start_date, nil] } }

        before do
          noteable.update!(start_date: start_date, due_date: due_date)
        end

        it 'sets the correct note message' do
          expect(note.note).to eq("removed start date #{start_date.to_fs(:long)} and removed due date #{due_date.to_fs(:long)}")
        end
      end

      context 'when due date is added' do
        let(:changed_dates) { { 'due_date' => [nil, due_date] } }

        it 'sets the correct note message' do
          expect(note.note).to eq("changed due date to #{due_date.to_fs(:long)}")
        end

        context 'and start date removed' do
          let(:changed_dates) { { 'due_date' => [nil, due_date], 'start_date' => [start_date, nil] } }

          it 'sets the correct note message' do
            expect(note.note).to eq("removed start date #{start_date.to_fs(:long)} and changed due date to #{due_date.to_fs(:long)}")
          end
        end
      end

      context 'when start_date is added' do
        let(:changed_dates) { { 'start_date' => [nil, start_date] } }

        it 'does not track the issue event' do
          expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_due_date_changed_action)

          subject
        end

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

          subject
        end

        it 'sets the correct note message' do
          expect(note.note).to eq("changed start date to #{start_date.to_fs(:long)}")
        end

        context 'and due date removed' do
          let(:changed_dates) { { 'due_date' => [due_date, nil], 'start_date' => [nil, start_date] } }

          it 'sets the correct note message' do
            expect(note.note).to eq("changed start date to #{start_date.to_fs(:long)} and removed due date #{due_date.to_fs(:long)}")
          end
        end
      end

      context 'when no dates are changed' do
        let(:changed_dates) { {} }

        it 'does not create a note and returns nil' do
          expect do
            note
          end.to not_change(Note, :count)

          expect(note).to be_nil
        end
      end
    end

    context 'when noteable is an issue' do
      let(:noteable) { issue }
      let(:activity_counter_class) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter }
      let(:activity_counter_method) { :track_issue_due_date_changed_action }

      it_behaves_like 'issuable getting date change notes'

      it 'does not track the work item event in usage ping' do
        expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter).not_to receive(:track_work_item_date_changed_action)

        subject
      end

      it 'tracks the issue event' do
        expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_due_date_changed_action)
                                                                           .with(author: author, project: project)

        subject
      end

      it_behaves_like 'internal event tracking' do
        let(:event) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_DUE_DATE_CHANGED }
        let(:user) { author }
        let(:namespace) { project.namespace }
      end

      context 'when only start_date is added' do
        let(:changed_dates) { { 'start_date' => [nil, start_date] } }

        it 'does not track the issue event in usage ping' do
          expect(activity_counter_class).not_to receive(activity_counter_method)

          subject
        end
      end
    end

    context 'when noteable is a work item' do
      let(:noteable) { work_item }
      let(:activity_counter_class) { Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter }
      let(:activity_counter_method) { :track_work_item_date_changed_action }

      it_behaves_like 'issuable getting date change notes'

      it 'does not track the issue event' do
        expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_due_date_changed_action)

        subject
      end

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

        subject
      end

      context 'when only start_date is added' do
        let(:changed_dates) { { 'start_date' => [nil, start_date] } }

        it 'tracks the issue event in usage ping' do
          expect(activity_counter_class).to receive(activity_counter_method).with(author: author)

          subject
        end
      end
    end

    context 'when noteable is a merge request' do
      let(:noteable) { create(:merge_request, source_project: project) }

      it 'does not track the issue event' do
        expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_due_date_changed_action)

        subject
      end

      it 'does not track the work item event in usage ping' do
        expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter).not_to receive(:track_work_item_date_changed_action)

        subject
      end

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

        subject
      end
    end
  end

  describe '#change_time_estimate' do
    subject { described_class.new(noteable: noteable, project: project, author: author).change_time_estimate }

    context 'when noteable is an issue' do
      let_it_be(:noteable, reload: true) { create(:issue, project: project) }

      it_behaves_like 'a system note' do
        let(:action) { 'time_tracking' }
      end

      context 'with a time estimate' do
        it 'sets the note text' do
          noteable.update_attribute(:time_estimate, 277200)

          expect(subject.note).to eq "changed time estimate to 1w 4d 5h"
        end

        context 'when time_tracking_limit_to_hours setting is true' do
          before do
            stub_application_setting(time_tracking_limit_to_hours: true)
          end

          it 'sets the note text' do
            noteable.update_attribute(:time_estimate, 277200)

            expect(subject.note).to eq "changed time estimate to 77h"
          end
        end
      end

      context 'without a time estimate' do
        it 'sets the note text' do
          expect(subject.note).to eq "removed time estimate"
        end
      end

      it 'tracks the issue event in usage ping' do
        expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_time_estimate_changed_action)
                                                                           .with(author: author, project: project)

        subject
      end

      it_behaves_like 'internal event tracking' do
        let(:event) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_TIME_ESTIMATE_CHANGED }
        let(:user) { author }
        let(:namespace) { project.namespace }
      end
    end

    context 'when noteable is a merge request' do
      let_it_be(:noteable) { create(:merge_request, source_project: project) }

      it 'does not track the issue event' do
        expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_time_estimate_changed_action)
                                                                               .with(author: author, project: project)

        subject
      end

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

        subject
      end
    end
  end

  describe '#create_timelog' do
    subject { described_class.new(noteable: noteable, project: project, author: author).created_timelog(timelog) }

    context 'when the timelog has a positive time spent value' do
      let_it_be(:noteable, reload: true) { create(:issue, project: project) }

      let(:timelog) { create(:timelog, user: author, issue: noteable, time_spent: 1800, spent_at: '2022-03-30T00:00:00.000Z') }

      it 'sets the note text' do
        expect(subject.note).to eq "added 30m of time spent at 2022-03-30"
      end
    end

    context 'when the timelog has a negative time spent value' do
      let_it_be(:noteable, reload: true) { create(:issue, project: project) }

      let(:timelog) { create(:timelog, user: author, issue: noteable, time_spent: -1800, spent_at: '2022-03-30T00:00:00.000Z') }

      it 'sets the note text' do
        expect(subject.note).to eq "subtracted 30m of time spent at 2022-03-30"
      end
    end
  end

  describe '#remove_timelog' do
    subject { described_class.new(noteable: noteable, project: project, author: author).remove_timelog(timelog) }

    context 'when the timelog has a positive time spent value' do
      let_it_be(:noteable, reload: true) { create(:issue, project: project) }

      let(:timelog) { create(:timelog, user: author, issue: noteable, time_spent: 1800, spent_at: '2022-03-30T00:00:00.000Z') }

      it 'sets the note text' do
        expect(subject.note).to eq "deleted 30m of spent time from 2022-03-30"
      end
    end

    context 'when the timelog has a negative time spent value' do
      let_it_be(:noteable, reload: true) { create(:issue, project: project) }

      let(:timelog) { create(:timelog, user: author, issue: noteable, time_spent: -1800, spent_at: '2022-03-30T00:00:00.000Z') }

      it 'sets the note text' do
        expect(subject.note).to eq "deleted -30m of spent time from 2022-03-30"
      end
    end
  end

  describe '#change_time_spent' do
    subject { described_class.new(noteable: noteable, project: project, author: author).change_time_spent }

    context 'when noteable is an issue' do
      let_it_be(:noteable, reload: true) { create(:issue, project: project) }

      it_behaves_like 'a system note' do
        let(:action) { 'time_tracking' }

        before do
          spend_time!(277200)
        end
      end

      context 'when time was added' do
        it 'sets the note text' do
          spend_time!(277200)

          expect(subject.note).to eq "added 1w 4d 5h of time spent"
        end

        context 'when time was subtracted' do
          it 'sets the note text' do
            spend_time!(360000)
            spend_time!(-277200)

            expect(subject.note).to eq "subtracted 1w 4d 5h of time spent"
          end
        end

        context 'when time was removed' do
          it 'sets the note text' do
            spend_time!(:reset)

            expect(subject.note).to eq "removed time spent"
          end
        end

        context 'when time_tracking_limit_to_hours setting is true' do
          before do
            stub_application_setting(time_tracking_limit_to_hours: true)
          end

          it 'sets the note text' do
            spend_time!(277200)

            expect(subject.note).to eq "added 77h of time spent"
          end
        end

        it 'tracks the issue event' do
          expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_time_spent_changed_action)
                                                                             .with(author: author, project: project)

          spend_time!(277200)

          subject
        end

        it_behaves_like 'internal event tracking' do
          let(:event) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_TIME_SPENT_CHANGED }
          let(:user) { author }
          let(:namespace) { project.namespace }
        end
      end

      context 'when noteable is a merge request' do
        let_it_be(:noteable) { create(:merge_request, source_project: project) }

        it 'does not track the issue event' do
          expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_time_estimate_changed_action)
                                                                                .with(author: author, project: project)

          spend_time!(277200)

          subject
        end

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

          subject
        end
      end

      def spend_time!(seconds)
        noteable.spend_time(duration: seconds, user_id: author.id)
        noteable.save!
      end
    end
  end
end


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

module SystemNotes
  class ZoomService < ::SystemNotes::BaseService
    def zoom_link_added
      create_note(NoteSummary.new(noteable, project, author, _('added a Zoom call to this issue'), action: 'pinned_embed'))
    end

    def zoom_link_removed
      create_note(NoteSummary.new(noteable, project, author, _('removed a Zoom call from this issue'), action: 'pinned_embed'))
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::SystemNotes::ZoomService, feature_category: :integrations do
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:author)  { create(:user) }

  let(:noteable)      { create(:issue, project: project) }

  let(:service) { described_class.new(noteable: noteable, project: project, author: author) }

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

    it_behaves_like 'a system note' do
      let(:action) { 'pinned_embed' }
    end

    it 'sets the zoom link added note text' do
      expect(subject.note).to eq('added a Zoom call to this issue')
    end
  end

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

    it_behaves_like 'a system note' do
      let(:action) { 'pinned_embed' }
    end

    it 'sets the zoom link removed note text' do
      expect(subject.note).to eq('removed a Zoom call from this issue')
    end
  end
end


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

module SystemNotes
  class IncidentService < ::SystemNotes::BaseService
    # Called when the severity of an Incident has changed
    #
    # Example Note text:
    #
    #   "changed the severity to Medium - S3"
    #
    # Returns the created Note object
    def change_incident_severity
      severity = noteable.severity

      if severity_label = IssuableSeverity::SEVERITY_LABELS[severity.to_sym]
        body = "changed the severity to **#{severity_label}**"

        create_note(NoteSummary.new(noteable, project, author, body, action: 'severity'))
      else
        Gitlab::AppLogger.error(
          message: 'Cannot create a system note for severity change',
          noteable_class: noteable.class.to_s,
          noteable_id: noteable.id,
          severity: severity
        )
      end
    end

    # Called when the status of an IncidentManagement::IssuableEscalationStatus has changed
    #
    # reason - String.
    #
    # Example Note text:
    #
    #   "changed the incident status to Acknowledged"
    #   "changed the incident status to Acknowledged by changing the status of ^alert#540"
    #
    # Returns the created Note object
    def change_incident_status(reason)
      status = noteable.escalation_status.status_name.to_s.titleize
      body = "changed the incident status to **#{status}**#{reason}"

      create_note(NoteSummary.new(noteable, project, author, body, action: 'status'))
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::SystemNotes::IncidentService, feature_category: :incident_management do
  let_it_be(:author) { create(:user) }
  let_it_be(:project) { create(:project) }
  let_it_be(:noteable) { create(:incident, project: project) }
  let_it_be(:issuable_severity) { create(:issuable_severity, issue: noteable, severity: :medium) }

  describe '#change_incident_severity' do
    subject(:change_severity) { described_class.new(noteable: noteable, project: project, author: author).change_incident_severity }

    before do
      allow(Gitlab::AppLogger).to receive(:error).and_call_original
    end

    it_behaves_like 'a system note' do
      let(:action) { 'severity' }
    end

    IssuableSeverity.severities.keys.each do |severity|
      context "with #{severity} severity" do
        before do
          issuable_severity.update!(severity: severity)
        end

        it 'has the appropriate message' do
          severity_label = IssuableSeverity::SEVERITY_LABELS.fetch(severity.to_sym)

          expect(change_severity.note).to eq("changed the severity to **#{severity_label}**")
        end
      end
    end

    context 'when severity is invalid' do
      let(:invalid_severity) { 'invalid-severity' }

      before do
        allow(noteable).to receive(:severity).and_return(invalid_severity)
      end

      it 'does not create system note' do
        expect { change_severity }.not_to change { noteable.notes.count }
      end

      it 'writes error to logs' do
        change_severity

        expect(Gitlab::AppLogger).to have_received(:error).with(
          message: 'Cannot create a system note for severity change',
          noteable_class: noteable.class.to_s,
          noteable_id: noteable.id,
          severity: invalid_severity
        )
      end
    end
  end

  describe '#change_incident_status' do
    let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: noteable) }

    let(:service) { described_class.new(noteable: noteable, project: project, author: author) }

    context 'with a provided reason' do
      subject(:change_incident_status) { service.change_incident_status(' by changing the alert status') }

      it 'creates a new note for an incident status change', :aggregate_failures do
        expect { change_incident_status }.to change { noteable.notes.count }.by(1)
        expect(noteable.notes.last.note).to eq("changed the incident status to **Triggered** by changing the alert status")
      end
    end

    context 'without provided reason' do
      subject(:change_incident_status) { service.change_incident_status(nil) }

      it 'creates a new note for an incident status change', :aggregate_failures do
        expect { change_incident_status }.to change { noteable.notes.count }.by(1)
        expect(noteable.notes.last.note).to eq("changed the incident status to **Triggered**")
      end
    end
  end
end


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

module Organizations
  class UpdateService < ::Organizations::BaseService
    attr_reader :organization

    def initialize(organization, current_user:, params: {})
      @organization = organization
      @current_user = current_user
      @params = params.dup
      return unless @params.key?(:description)

      organization_detail_attributes = { description: @params.delete(:description) }
      # TODO: Remove explicit passing of id once https://github.com/rails/rails/issues/48714 is resolved.
      organization_detail_attributes[:id] = organization.id
      @params[:organization_detail_attributes] ||= {}
      @params[:organization_detail_attributes].merge!(organization_detail_attributes)
    end

    def execute
      return error_no_permissions unless allowed?

      if organization.update(params)
        ServiceResponse.success(payload: organization)
      else
        error_updating
      end
    end

    private

    def allowed?
      current_user&.can?(:admin_organization, organization)
    end

    def error_no_permissions
      ServiceResponse.error(message: [_('You have insufficient permissions to update the organization')])
    end

    def error_updating
      message = organization.errors.full_messages || _('Failed to update organization')

      ServiceResponse.error(payload: organization, message: Array(message))
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Organizations::UpdateService, feature_category: :cell do
  describe '#execute' do
    let_it_be(:user) { create(:user) }
    let_it_be_with_reload(:organization) { create(:organization) }

    let(:current_user) { user }
    let(:name) { 'Name' }
    let(:path) { 'path' }
    let(:description) { nil }
    let(:params) { { name: name, path: path } }

    subject(:response) do
      described_class.new(organization, current_user: current_user, params: params).execute
    end

    context 'when user does not have permission' do
      let(:current_user) { nil }

      it 'returns an error' do
        expect(response).to be_error

        expect(response.message).to match_array(['You have insufficient permissions to update the organization'])
      end
    end

    context 'when user has permission' do
      before do
        create(:organization_user, organization: organization, user: current_user)
      end

      shared_examples 'updating an organization' do
        it 'updates the organization' do
          response
          organization.reset

          expect(response).to be_success
          expect(organization.name).to eq(name)
          expect(organization.path).to eq(path)
          expect(organization.description).to eq(description)
        end
      end

      context 'with description' do
        let(:description) { 'Organization description' }
        let(:params) do
          {
            name: name,
            path: path,
            description: description
          }
        end

        it_behaves_like 'updating an organization'
      end

      include_examples 'updating an organization'

      it 'returns an error when the organization is not updated' do
        params[:name] = nil

        expect(response).to be_error
        expect(response.message).to match_array(["Name can't be blank"])
      end
    end
  end
end


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

module Organizations
  class CreateService < ::Organizations::BaseService
    def execute
      return error_no_permissions unless current_user&.can?(:create_organization)

      organization = Organization.create(params)

      return error_creating(organization) unless organization.persisted?

      ServiceResponse.success(payload: organization)
    end

    private

    def error_no_permissions
      ServiceResponse.error(message: [_('You have insufficient permissions to create organizations')])
    end

    def error_creating(organization)
      message = organization.errors.full_messages || _('Failed to create organization')

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

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Organizations::CreateService, feature_category: :cell do
  describe '#execute' do
    let_it_be(:user) { create(:user) }

    let(:current_user) { user }
    let(:params) { attributes_for(:organization) }

    subject(:response) { described_class.new(current_user: current_user, params: params).execute }

    context 'when user does not have permission' do
      let(:current_user) { nil }

      it 'returns an error' do
        expect(response).to be_error

        expect(response.message).to match_array(
          ['You have insufficient permissions to create organizations'])
      end
    end

    context 'when user has permission' do
      it 'creates an organization' do
        expect { response }.to change { Organizations::Organization.count }

        expect(response).to be_success
      end

      it 'returns an error when the organization is not persisted' do
        params[:name] = nil

        expect(response).to be_error
        expect(response.message).to match_array(["Name can't be blank"])
      end
    end
  end
end


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

module Terraform
  class RemoteStateHandler < BaseService
    include Gitlab::OptimisticLocking

    StateLockedError = Class.new(StandardError)
    StateDeletedError = Class.new(StandardError)
    UnauthorizedError = Class.new(StandardError)

    def find_with_lock
      retrieve_with_lock(find_only: true) do |state|
        yield state if block_given?
      end
    end

    def handle_with_lock
      raise UnauthorizedError unless can_modify_state?

      retrieve_with_lock do |state|
        raise StateLockedError unless lock_matches?(state)

        yield state if block_given?

        state.save! unless state.destroyed?
      end

      nil
    end

    def lock!
      raise ArgumentError if params[:lock_id].blank?
      raise UnauthorizedError unless can_modify_state?

      retrieve_with_lock do |state|
        raise StateLockedError if state.locked?

        state.lock_xid = params[:lock_id]
        state.locked_by_user = current_user
        state.locked_at = Time.current

        state.save!
      end
    end

    def unlock!
      raise UnauthorizedError unless can_modify_state?

      retrieve_with_lock do |state|
        # force-unlock does not pass ID, so we ignore it if it is missing
        raise StateLockedError unless params[:lock_id].nil? || lock_matches?(state)

        state.lock_xid = nil
        state.locked_by_user = nil
        state.locked_at = nil

        state.save!
      end
    end

    private

    def retrieve_with_lock(find_only: false)
      create_or_find!(find_only: find_only).tap do |state|
        retry_lock(state, name: "Terraform state: #{state.id}") { yield state }
      end
    end

    def create_or_find!(find_only:)
      raise ArgumentError unless params[:name].present?

      find_params = { project: project, name: params[:name] }

      state = if find_only
                find_state!(find_params)
              else
                Terraform::State.safe_find_or_create_by(find_params)
              end

      raise StateDeletedError if state.deleted_at?

      state
    end

    def lock_matches?(state)
      return true if state.lock_xid.nil? && params[:lock_id].nil?

      ActiveSupport::SecurityUtils
        .secure_compare(state.lock_xid.to_s, params[:lock_id].to_s)
    end

    def can_modify_state?
      current_user.can?(:admin_terraform_state, project)
    end

    def find_state(find_params)
      Terraform::State.find_by(find_params) # rubocop: disable CodeReuse/ActiveRecord
    end

    def find_state!(find_params)
      find_state(find_params) || raise(ActiveRecord::RecordNotFound, "Couldn't find state")
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Terraform::RemoteStateHandler, feature_category: :infrastructure_as_code do
  let_it_be(:project) { create(:project) }
  let_it_be(:developer) { create(:user, developer_projects: [project]) }
  let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) }

  let_it_be(:user) { maintainer }

  describe '#find_with_lock' do
    context 'without a state name' do
      subject { described_class.new(project, user) }

      it 'raises an exception' do
        expect { subject.find_with_lock }.to raise_error(ArgumentError)
      end
    end

    context 'with a state name' do
      subject { described_class.new(project, user, name: 'state') }

      context 'with no matching state' do
        it 'raises an exception' do
          expect { subject.find_with_lock }.to raise_error(ActiveRecord::RecordNotFound)
        end
      end

      context 'with a matching state' do
        let!(:state) { create(:terraform_state, project: project, name: 'state') }

        it 'returns the state' do
          expect(subject.find_with_lock).to eq(state)
        end

        context 'with a state scheduled for deletion' do
          let!(:state) { create(:terraform_state, :deletion_in_progress, project: project, name: 'state') }

          it 'raises an exception' do
            expect { subject.find_with_lock }.to raise_error(described_class::StateDeletedError)
          end
        end
      end
    end
  end

  context 'when state locking is not being used' do
    subject { described_class.new(project, user, name: 'my-state') }

    describe '#handle_with_lock' do
      it 'allows to modify a state using database locking' do
        record = nil
        subject.handle_with_lock do |state|
          record = state
          state.name = 'updated-name'
        end

        expect(record.reload.name).to eq 'updated-name'
      end

      it 'returns nil' do
        expect(subject.handle_with_lock).to be_nil
      end
    end

    describe '#lock!' do
      it 'raises an error' do
        expect { subject.lock! }.to raise_error(ArgumentError)
      end
    end
  end

  context 'when using locking' do
    describe '#handle_with_lock' do
      subject(:handler) { described_class.new(project, user, name: 'new-state', lock_id: 'abc-abc') }

      it 'handles a locked state using exclusive read lock' do
        handler.lock!

        record = nil
        handler.handle_with_lock do |state|
          record = state
          state.name = 'new-name'
        end

        expect(record.reload.name).to eq 'new-name'
        expect(record.reload.project).to eq project
      end

      it 'raises exception if lock has not been acquired before' do
        expect { handler.handle_with_lock }
          .to raise_error(described_class::StateLockedError)
      end

      it 'raises an exception if the state is scheduled for deletion' do
        create(:terraform_state, :deletion_in_progress, project: project, name: 'new-state')

        expect { handler.handle_with_lock }
          .to raise_error(described_class::StateDeletedError)
      end

      context 'user does not have permission to modify state' do
        let(:user) { developer }

        it 'raises an exception' do
          expect { handler.handle_with_lock }
            .to raise_error(described_class::UnauthorizedError)
        end
      end
    end

    describe '#lock!' do
      let(:lock_id) { 'abc-abc' }

      subject(:handler) do
        described_class.new(
          project,
          user,
          name: 'new-state',
          lock_id: lock_id
        )
      end

      it 'allows to lock state if it does not exist yet' do
        state = handler.lock!

        expect(state).to be_persisted
        expect(state.name).to eq 'new-state'
      end

      it 'allows to lock state if it exists and is not locked' do
        state = create(:terraform_state, project: project, name: 'new-state')

        handler.lock!

        expect(state.reload.lock_xid).to eq lock_id
        expect(state).to be_locked
      end

      it 'raises an exception when trying to unlocked state locked by someone else' do
        described_class.new(project, user, name: 'new-state', lock_id: '12a-23f').lock!

        expect { handler.lock! }.to raise_error(described_class::StateLockedError)
      end

      it 'raises an exception when the state exists and is scheduled for deletion' do
        create(:terraform_state, :deletion_in_progress, project: project, name: 'new-state')

        expect { handler.lock! }.to raise_error(described_class::StateDeletedError)
      end
    end

    describe '#unlock!' do
      let_it_be(:state) { create(:terraform_state, :locked, project: project, name: 'new-state', lock_xid: 'abc-abc') }

      let(:lock_id) { state.lock_xid }

      subject(:handler) do
        described_class.new(
          project,
          user,
          name: state.name,
          lock_id: lock_id
        )
      end

      it 'unlocks the state' do
        state = handler.unlock!

        expect(state.lock_xid).to be_nil
      end

      context 'with no lock ID (force-unlock)' do
        let(:lock_id) {}

        it 'unlocks the state' do
          state = handler.unlock!

          expect(state.lock_xid).to be_nil
        end
      end

      context 'with different lock ID' do
        let(:lock_id) { 'other' }

        it 'raises an exception' do
          expect { handler.unlock! }
            .to raise_error(described_class::StateLockedError)
        end
      end

      context 'with a state scheduled for deletion' do
        it 'raises an exception' do
          state.update!(deleted_at: Time.current)

          expect { handler.unlock! }
            .to raise_error(described_class::StateDeletedError)
        end
      end
    end
  end
end


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

module Terraform
  module States
    class TriggerDestroyService
      def initialize(state, current_user:)
        @state = state
        @current_user = current_user
      end

      def execute
        return unauthorized_response unless can_destroy_state?
        return state_locked_response if state.locked?

        state.run_after_commit do
          Terraform::States::DestroyWorker.perform_async(id)
        end

        state.update!(deleted_at: Time.current)

        ServiceResponse.success
      end

      private

      attr_reader :state, :current_user

      def can_destroy_state?
        current_user.can?(:admin_terraform_state, state.project)
      end

      def unauthorized_response
        error_response(s_('Terraform|You have insufficient permissions to delete this state'))
      end

      def state_locked_response
        error_response(s_('Terraform|Cannot remove a locked state'))
      end

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

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Terraform::States::TriggerDestroyService, feature_category: :infrastructure_as_code do
  let_it_be(:project) { create(:project) }
  let_it_be(:user) { create(:user, maintainer_projects: [project]) }

  describe '#execute', :aggregate_failures do
    let_it_be(:state) { create(:terraform_state, project: project) }

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

    subject { service.execute }

    it 'marks the state as deleted and schedules a cleanup worker' do
      expect(Terraform::States::DestroyWorker).to receive(:perform_async).with(state.id).once

      expect(subject).to be_success
      expect(state.deleted_at).to be_like_time(Time.current)
    end

    context 'within a database transaction' do
      subject { state.with_lock { service.execute } }

      it 'does not raise an EnqueueFromTransactionError' do
        expect { subject }.not_to raise_error
        expect(state.deleted_at).to be_like_time(Time.current)
      end
    end

    shared_examples 'unable to delete state' do
      it 'does not modify the state' do
        expect(Terraform::States::DestroyWorker).not_to receive(:perform_async)

        expect { subject }.not_to change(state, :deleted_at)
        expect(subject).to be_error
        expect(subject.message).to eq(message)
      end
    end

    context 'user does not have permission' do
      let(:user) { create(:user, developer_projects: [project]) }
      let(:message) { 'You have insufficient permissions to delete this state' }

      include_examples 'unable to delete state'
    end

    context 'state is locked' do
      let(:state) { create(:terraform_state, :locked, project: project) }
      let(:message) { 'Cannot remove a locked state' }

      include_examples 'unable to delete state'
    end
  end
end


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

module Terraform
  module States
    class DestroyService
      def initialize(state)
        @state = state
      end

      def execute
        return unless state.deleted_at?

        state.versions.each_batch(column: :version) do |batch|
          process_batch(batch)
        end

        state.destroy!
      end

      private

      attr_reader :state

      # Overridden in EE
      def process_batch(batch)
        batch.each do |version|
          version.file.remove!
        end
      end
    end
  end
end

Terraform::States::DestroyService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Terraform::States::DestroyService, feature_category: :infrastructure_as_code do
  let_it_be(:state) { create(:terraform_state, :with_version, :deletion_in_progress) }

  let(:file) { instance_double(Terraform::StateUploader, relative_path: 'path') }

  before do
    allow_next_found_instance_of(Terraform::StateVersion) do |version|
      allow(version).to receive(:file).and_return(file)
    end
  end

  describe '#execute' do
    subject { described_class.new(state).execute }

    it 'removes version files from object storage, followed by the state record' do
      expect(file).to receive(:remove!).once
      expect(state).to receive(:destroy!)

      subject
    end

    context 'state is not marked for deletion' do
      let(:state) { create(:terraform_state) }

      it 'does not delete the state' do
        expect(state).not_to receive(:destroy!)

        subject
      end
    end
  end
end


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

module Snippets
  class UpdateRepositoryStorageService
    include UpdateRepositoryStorageMethods

    delegate :snippet, to: :repository_storage_move

    private

    def track_repository(destination_storage_name)
      snippet.track_snippet_repository(destination_storage_name)
    end

    def mirror_repositories
      return unless snippet.repository_exists?

      mirror_repository(type: Gitlab::GlRepository::SNIPPET)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Snippets::UpdateRepositoryStorageService, feature_category: :source_code_management do
  subject { described_class.new(repository_storage_move) }

  describe "#execute" do
    let_it_be_with_reload(:snippet) { create(:snippet, :repository) }
    let_it_be(:destination) { 'test_second_storage' }
    let_it_be(:checksum) { snippet.repository.checksum }

    let(:repository_storage_move_state) { :scheduled }
    let(:repository_storage_move) { create(:snippet_repository_storage_move, repository_storage_move_state, container: snippet, destination_storage_name: destination) }
    let(:snippet_repository_double) { double(:repository) }
    let(:original_snippet_repository_double) { double(:repository) }

    before do
      allow(Gitlab.config.repositories.storages).to receive(:keys).and_return(%w[default test_second_storage])
      allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
      allow(Gitlab::GitalyClient).to receive(:filesystem_id).with(destination).and_return(SecureRandom.uuid)
      allow(Gitlab::Git::Repository).to receive(:new).and_call_original
      allow(Gitlab::Git::Repository).to receive(:new)
        .with(destination, snippet.repository.raw.relative_path, snippet.repository.gl_repository, snippet.repository.full_path)
        .and_return(snippet_repository_double)
      allow(Gitlab::Git::Repository).to receive(:new)
        .with('default', snippet.repository.raw.relative_path, nil, nil)
        .and_return(original_snippet_repository_double)
    end

    context 'when the move succeeds' do
      it 'moves the repository to the new storage and unmarks the repository as read-only' do
        expect(snippet_repository_double).to receive(:replicate)
          .with(snippet.repository.raw)
        expect(snippet_repository_double).to receive(:checksum)
          .and_return(checksum)
        expect(original_snippet_repository_double).to receive(:remove)

        result = subject.execute
        snippet.reload

        expect(result).to be_success
        expect(snippet).not_to be_repository_read_only
        expect(snippet.repository_storage).to eq(destination)
        expect(snippet.snippet_repository.shard_name).to eq(destination)
        expect(repository_storage_move.reload).to be_finished
        expect(repository_storage_move.error_message).to be_nil
      end
    end

    context 'when the filesystems are the same' do
      before do
        expect(Gitlab::GitalyClient).to receive(:filesystem_id).twice.and_return(SecureRandom.uuid)
      end

      it 'updates the database without trying to move the repostory', :aggregate_failures do
        result = subject.execute
        snippet.reload

        expect(result).to be_success
        expect(snippet).not_to be_repository_read_only
        expect(snippet.repository_storage).to eq(destination)
        expect(snippet.snippet_repository.shard_name).to eq(destination)
      end
    end

    context 'when the move fails' do
      it 'unmarks the repository as read-only without updating the repository storage' do
        expect(snippet_repository_double).to receive(:replicate)
          .with(snippet.repository.raw)
          .and_raise(Gitlab::Git::CommandError, 'Boom')
        expect(snippet_repository_double).to receive(:remove)

        expect do
          subject.execute
        end.to raise_error(Gitlab::Git::CommandError)

        expect(snippet).not_to be_repository_read_only
        expect(snippet.repository_storage).to eq('default')
        expect(repository_storage_move).to be_failed
        expect(repository_storage_move.error_message).to eq('Boom')
      end
    end

    context 'when the cleanup fails' do
      it 'sets the correct state' do
        expect(snippet_repository_double).to receive(:replicate)
          .with(snippet.repository.raw)
        expect(snippet_repository_double).to receive(:checksum)
          .and_return(checksum)
        expect(original_snippet_repository_double).to receive(:remove)
          .and_raise(Gitlab::Git::CommandError)

        expect do
          subject.execute
        end.to raise_error(Gitlab::Git::CommandError)

        expect(repository_storage_move).to be_cleanup_failed
      end
    end

    context 'when the checksum does not match' do
      it 'unmarks the repository as read-only without updating the repository storage' do
        expect(snippet_repository_double).to receive(:replicate)
          .with(snippet.repository.raw)
        expect(snippet_repository_double).to receive(:checksum)
          .and_return('not matching checksum')
        expect(snippet_repository_double).to receive(:remove)

        expect do
          subject.execute
        end.to raise_error(Repositories::ReplicateService::Error, /Failed to verify snippet repository checksum from \w+ to not matching checksum/)

        expect(snippet).not_to be_repository_read_only
        expect(snippet.repository_storage).to eq('default')
      end
    end

    context 'when the repository move is finished' do
      let(:repository_storage_move_state) { :finished }

      it 'is idempotent' do
        expect do
          result = subject.execute

          expect(result).to be_success
        end.not_to change(repository_storage_move, :state)
      end
    end

    context 'when the repository move is failed' do
      let(:repository_storage_move_state) { :failed }

      it 'is idempotent' do
        expect do
          result = subject.execute

          expect(result).to be_success
        end.not_to change(repository_storage_move, :state)
      end
    end
  end
end