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

# Service class for creating push event payloads as stored in the
# "push_event_payloads" table.
#
# Example:
#
#     data = Gitlab::DataBuilder::Push.build(...)
#     event = Event.create(...)
#
#     PushEventPayloadService.new(event, data).execute
class PushEventPayloadService
  # event - The event this push payload belongs to.
  # push_data - A Hash produced by `Gitlab::DataBuilder::Push.build` to use for
  #             building the push payload.
  def initialize(event, push_data)
    @event = event
    @push_data = push_data
  end

  # Creates and returns a new PushEventPayload row.
  #
  # This method will raise upon encountering validation errors.
  #
  # Returns an instance of PushEventPayload.
  def execute
    @event.build_push_event_payload(
      commit_count: commit_count,
      action: action,
      ref_type: ref_type,
      commit_from: commit_from_id,
      commit_to: commit_to_id,
      ref: trimmed_ref,
      commit_title: commit_title,
      event_id: @event.id
    )

    @event.push_event_payload.save!
    @event.push_event_payload
  end

  # Returns the commit title to use.
  #
  # The commit title is limited to the first line and a maximum of 70
  # characters.
  def commit_title
    commit = @push_data.fetch(:commits).last

    return unless commit && commit[:message]

    raw_msg = commit[:message]

    # Find where the first line ends, without turning the entire message into an
    # Array of lines (this is a waste of memory for large commit messages).
    index = raw_msg.index("\n")
    message = index ? raw_msg[0..index] : raw_msg

    message.strip.truncate(70)
  end

  def commit_from_id
    if create?
      nil
    else
      revision_before
    end
  end

  def commit_to_id
    if remove?
      nil
    else
      revision_after
    end
  end

  def commit_count
    @push_data.fetch(:total_commits_count)
  end

  def ref
    @push_data.fetch(:ref)
  end

  def revision_before
    @push_data.fetch(:before)
  end

  def revision_after
    @push_data.fetch(:after)
  end

  def trimmed_ref
    Gitlab::Git.ref_name(ref)
  end

  def create?
    Gitlab::Git.blank_ref?(revision_before)
  end

  def remove?
    Gitlab::Git.blank_ref?(revision_after)
  end

  def action
    if create?
      :created
    elsif remove?
      :removed
    else
      :pushed
    end
  end

  def ref_type
    if Gitlab::Git.tag_ref?(ref)
      :tag
    else
      :branch
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe PushEventPayloadService, feature_category: :source_code_management do
  let(:event) { create(:push_event) }

  describe '#execute' do
    let(:push_data) do
      {
        commits: [
          {
            id: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
            message: 'This is a commit'
          }
        ],
        before: '0000000000000000000000000000000000000000',
        after: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
        total_commits_count: 1,
        ref: 'refs/heads/my-branch'
      }
    end

    it 'creates a new PushEventPayload row' do
      payload = described_class.new(event, push_data).execute

      expect(payload.commit_count).to eq(1)
      expect(payload.action).to eq('created')
      expect(payload.ref_type).to eq('branch')
      expect(payload.commit_from).to be_nil
      expect(payload.commit_to).to eq(push_data[:after])
      expect(payload.ref).to eq('my-branch')
      expect(payload.commit_title).to eq('This is a commit')
      expect(payload.event_id).to eq(event.id)
    end

    it 'sets the push_event_payload association of the used event' do
      payload = described_class.new(event, push_data).execute

      expect(event.push_event_payload).to eq(payload)
    end
  end

  describe '#commit_title' do
    it 'returns nil if no commits were pushed' do
      service = described_class.new(event, commits: [])

      expect(service.commit_title).to be_nil
    end

    it 'returns a String limited to 70 characters' do
      service = described_class.new(event, commits: [{ message: 'a' * 100 }])

      expect(service.commit_title).to eq(('a' * 67) + '...')
    end

    it 'does not truncate the commit message if it is shorter than 70 characters' do
      service = described_class.new(event, commits: [{ message: 'Hello' }])

      expect(service.commit_title).to eq('Hello')
    end

    it 'includes the first line of a commit message if the message spans multiple lines' do
      service = described_class
        .new(event, commits: [{ message: "Hello\n\nworld" }])

      expect(service.commit_title).to eq('Hello')
    end
  end

  describe '#commit_from_id' do
    it 'returns nil when creating a new ref' do
      service = described_class.new(event, before: Gitlab::Git::BLANK_SHA)

      expect(service.commit_from_id).to be_nil
    end

    it 'returns the ID of the first commit when pushing to an existing ref' do
      service = described_class.new(event, before: '123')

      expect(service.commit_from_id).to eq('123')
    end
  end

  describe '#commit_to_id' do
    it 'returns nil when removing an existing ref' do
      service = described_class.new(event, after: Gitlab::Git::BLANK_SHA)

      expect(service.commit_to_id).to be_nil
    end
  end

  describe '#commit_count' do
    it 'returns the number of commits' do
      service = described_class.new(event, total_commits_count: 1)

      expect(service.commit_count).to eq(1)
    end

    it 'raises when the push data does not contain the commits count' do
      service = described_class.new(event, {})

      expect { service.commit_count }.to raise_error(KeyError)
    end
  end

  describe '#ref' do
    it 'returns the name of the ref' do
      service = described_class.new(event, ref: 'refs/heads/foo')

      expect(service.ref).to eq('refs/heads/foo')
    end

    it 'raises when the push data does not contain the ref name' do
      service = described_class.new(event, {})

      expect { service.ref }.to raise_error(KeyError)
    end
  end

  describe '#revision_before' do
    it 'returns the revision from before the push' do
      service = described_class.new(event, before: 'foo')

      expect(service.revision_before).to eq('foo')
    end

    it 'raises when the push data does not contain the before revision' do
      service = described_class.new(event, {})

      expect { service.revision_before }.to raise_error(KeyError)
    end
  end

  describe '#revision_after' do
    it 'returns the revision from after the push' do
      service = described_class.new(event, after: 'foo')

      expect(service.revision_after).to eq('foo')
    end

    it 'raises when the push data does not contain the after revision' do
      service = described_class.new(event, {})

      expect { service.revision_after }.to raise_error(KeyError)
    end
  end

  describe '#trimmed_ref' do
    it 'returns the ref name without its prefix' do
      service = described_class.new(event, ref: 'refs/heads/foo')

      expect(service.trimmed_ref).to eq('foo')
    end
  end

  describe '#create?' do
    it 'returns true when creating a new ref' do
      service = described_class.new(event, before: Gitlab::Git::BLANK_SHA)

      expect(service.create?).to eq(true)
    end

    it 'returns false when pushing to an existing ref' do
      service = described_class.new(event, before: 'foo')

      expect(service.create?).to eq(false)
    end
  end

  describe '#remove?' do
    it 'returns true when removing an existing ref' do
      service = described_class.new(event, after: Gitlab::Git::BLANK_SHA)

      expect(service.remove?).to eq(true)
    end

    it 'returns false pushing to an existing ref' do
      service = described_class.new(event, after: 'foo')

      expect(service.remove?).to eq(false)
    end
  end

  describe '#action' do
    it 'returns :created when creating a ref' do
      service = described_class.new(event, before: Gitlab::Git::BLANK_SHA)

      expect(service.action).to eq(:created)
    end

    it 'returns :removed when removing an existing ref' do
      service = described_class.new(event, before: '123', after: Gitlab::Git::BLANK_SHA)

      expect(service.action).to eq(:removed)
    end

    it 'returns :pushed when pushing to an existing ref' do
      service = described_class.new(event, before: '123', after: '456')

      expect(service.action).to eq(:pushed)
    end
  end

  describe '#ref_type' do
    it 'returns :tag for a tag' do
      service = described_class.new(event, ref: 'refs/tags/1.2')

      expect(service.ref_type).to eq(:tag)
    end

    it 'returns :branch for a branch' do
      service = described_class.new(event, ref: 'refs/heads/master')

      expect(service.ref_type).to eq(:branch)
    end
  end
end


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

require 'resolv'

class VerifyPagesDomainService < BaseService
  # The maximum number of seconds to be spent on each DNS lookup
  RESOLVER_TIMEOUT_SECONDS = 15

  # How long verification lasts for
  VERIFICATION_PERIOD = 7.days
  REMOVAL_DELAY = 1.week.freeze

  attr_reader :domain

  def initialize(domain)
    @domain = domain
  end

  def execute
    return error("No verification code set for #{domain.domain}") unless domain.verification_code.present?

    if !verification_enabled? || dns_record_present?
      verify_domain!
    elsif expired?
      disable_domain!
    else
      unverify_domain!
    end
  end

  private

  def verify_domain!
    was_disabled = !domain.enabled?
    was_unverified = domain.unverified?

    # Prevent any pre-existing grace period from being truncated
    reverify = [domain.enabled_until, VERIFICATION_PERIOD.from_now].compact.max

    domain.assign_attributes(verified_at: Time.current, enabled_until: reverify, remove_at: nil)
    domain.save!(validate: false)

    if was_disabled
      notify(:enabled)
    elsif was_unverified
      notify(:verification_succeeded)
    end

    after_successful_verification

    success
  end

  def after_successful_verification
    # method overridden in EE
  end

  def unverify_domain!
    was_verified = domain.verified?

    domain.assign_attributes(verified_at: nil)
    domain.remove_at ||= REMOVAL_DELAY.from_now unless domain.enabled?
    domain.save!(validate: false)

    notify(:verification_failed) if was_verified

    error("Couldn't verify #{domain.domain}")
  end

  def disable_domain!
    domain.assign_attributes(verified_at: nil, enabled_until: nil)
    domain.remove_at ||= REMOVAL_DELAY.from_now
    domain.save!(validate: false)

    notify(:disabled)

    error("Couldn't verify #{domain.domain}. It is now disabled.")
  end

  # A domain is only expired until `disable!` has been called
  def expired?
    domain.enabled_until&.past?
  end

  def dns_record_present?
    Resolv::DNS.open do |resolver|
      resolver.timeouts = RESOLVER_TIMEOUT_SECONDS

      check(domain.domain, resolver) || check(domain.verification_domain, resolver)
    end
  end

  def check(domain_name, resolver)
    # Append '.' to domain_name, indicating absolute FQDN
    records = parse(txt_records(domain_name + '.', resolver))

    records.any? do |record|
      record == domain.keyed_verification_code || record == domain.verification_code
    end
  rescue StandardError => err
    log_error("Failed to check TXT records on #{domain_name} for #{domain.domain}: #{err}")
    false
  end

  def txt_records(domain_name, resolver)
    resolver.getresources(domain_name, Resolv::DNS::Resource::IN::TXT)
  end

  def parse(records)
    records.flat_map(&:strings).flat_map(&:split)
  end

  def verification_enabled?
    Gitlab::CurrentSettings.pages_domain_verification_enabled?
  end

  def notify(type)
    return unless verification_enabled?

    Gitlab::AppLogger.info("Pages domain '#{domain.domain}' changed state to '#{type}'")
    notification_service.public_send("pages_domain_#{type}", domain) # rubocop:disable GitlabSecurity/PublicSend
  end
end

VerifyPagesDomainService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe VerifyPagesDomainService, feature_category: :pages do
  using RSpec::Parameterized::TableSyntax
  include EmailHelpers

  let(:error_status) { { status: :error, message: "Couldn't verify #{domain.domain}" } }

  subject(:service) { described_class.new(domain) }

  describe '#execute' do
    where(:domain_sym, :code_sym) do
      :domain | :verification_code
      :domain | :keyed_verification_code

      :verification_domain | :verification_code
      :verification_domain | :keyed_verification_code
    end

    with_them do
      let(:domain_name) { domain.send(domain_sym) }
      let(:verification_code) { domain.send(code_sym) }

      shared_examples 'verifies and enables the domain' do
        it 'verifies and enables the domain' do
          expect(service.execute).to eq(status: :success)

          expect(domain).to be_verified
          expect(domain).to be_enabled
          expect(domain.remove_at).to be_nil
        end
      end

      shared_examples 'successful enablement and verification' do
        context 'when txt record contains verification code' do
          before do
            stub_resolver(domain_name => ['something else', verification_code])
          end

          include_examples 'verifies and enables the domain'
        end

        context 'when txt record contains verification code with other text' do
          before do
            stub_resolver(domain_name => "something #{verification_code} else")
          end

          include_examples 'verifies and enables the domain'
        end
      end

      shared_examples 'unverifies and disables domain' do
        it 'unverifies domain' do
          expect(service.execute).to eq(error_status)
          expect(domain).not_to be_verified
        end

        it 'disables domain and shedules it for removal in 1 week' do
          service.execute

          expect(domain).not_to be_enabled

          expect(domain.remove_at).to be_like_time(7.days.from_now)
        end
      end

      context 'when domain is disabled(or new)' do
        let(:domain) { create(:pages_domain, :disabled) }

        include_examples 'successful enablement and verification'

        context 'when txt record does not contain verification code' do
          before do
            stub_resolver(domain_name => 'something else')
          end

          include_examples 'unverifies and disables domain'
        end

        context 'when txt record does not contain verification code' do
          before do
            stub_resolver(domain_name => 'something else')
          end

          include_examples 'unverifies and disables domain'
        end

        context 'when no txt records are present' do
          before do
            stub_resolver
          end

          include_examples 'unverifies and disables domain'
        end
      end

      context 'when domain is verified' do
        let(:domain) { create(:pages_domain) }

        include_examples 'successful enablement and verification'

        shared_examples 'unverifing domain' do
          it 'unverifies but does not disable domain' do
            expect(service.execute).to eq(error_status)
            expect(domain).not_to be_verified
            expect(domain).to be_enabled
          end

          it 'does not schedule domain for removal' do
            service.execute
            expect(domain.remove_at).to be_nil
          end
        end

        context 'when txt record does not contain verification code' do
          before do
            stub_resolver(domain_name => 'something else')
          end

          include_examples 'unverifing domain'
        end

        context 'when no txt records are present' do
          before do
            stub_resolver
          end

          include_examples 'unverifing domain'
        end
      end

      context 'when domain is expired' do
        let(:domain) { create(:pages_domain, :expired) }

        context 'when the right code is present' do
          before do
            stub_resolver(domain_name => domain.keyed_verification_code)
          end

          include_examples 'verifies and enables the domain'
        end

        context 'when the right code is not present' do
          before do
            stub_resolver
          end

          let(:error_status) { { status: :error, message: "Couldn't verify #{domain.domain}. It is now disabled." } }

          include_examples 'unverifies and disables domain'
        end
      end

      context 'when domain is disabled and scheduled for removal' do
        let(:domain) { create(:pages_domain, :disabled, :scheduled_for_removal) }

        context 'when the right code is present' do
          before do
            stub_resolver(domain.domain => domain.keyed_verification_code)
          end

          it 'verifies and enables domain' do
            expect(service.execute).to eq(status: :success)

            expect(domain).to be_verified
            expect(domain).to be_enabled
          end

          it 'prevent domain from being removed' do
            expect { service.execute }.to change { domain.remove_at }.to(nil)
          end
        end

        context 'when the right code is not present' do
          before do
            stub_resolver
          end

          it 'keeps domain scheduled for removal but does not change removal time' do
            expect { service.execute }.not_to change { domain.remove_at }
            expect(domain.remove_at).to be_present
          end
        end
      end

      context 'invalid domain' do
        let(:domain) { build(:pages_domain, :expired, :with_missing_chain) }

        before do
          domain.save!(validate: false)
        end

        it 'can be disabled' do
          error_status[:message] += '. It is now disabled.'

          stub_resolver

          expect(service.execute).to eq(error_status)

          expect(domain).not_to be_verified
          expect(domain).not_to be_enabled
        end
      end
    end

    context 'timeout behaviour' do
      let(:domain) { create(:pages_domain) }

      it 'sets a timeout on the DNS query' do
        expect(stub_resolver).to receive(:timeouts=).with(described_class::RESOLVER_TIMEOUT_SECONDS)

        service.execute
      end
    end

    context 'email notifications' do
      let(:notification_service) { instance_double('NotificationService') }

      where(:factory, :verification_succeeds, :expected_notification) do
        nil         | true  | nil
        nil         | false | :verification_failed
        :reverify   | true  | nil
        :reverify   | false | :verification_failed
        :unverified | true  | :verification_succeeded
        :unverified | false | nil
        :expired    | true  | nil
        :expired    | false | :disabled
        :disabled   | true  | :enabled
        :disabled   | false | nil
      end

      with_them do
        let(:domain) { create(:pages_domain, *[factory].compact) }

        before do
          allow(service).to receive(:notification_service) { notification_service }

          if verification_succeeds
            stub_resolver(domain.domain => domain.verification_code)
          else
            stub_resolver
          end
        end

        it 'sends a notification if appropriate' do
          if expected_notification
            expect(notification_service).to receive(:"pages_domain_#{expected_notification}").with(domain)
          end

          service.execute
        end
      end

      context 'pages verification disabled' do
        let(:domain) { create(:pages_domain, :disabled) }

        before do
          stub_application_setting(pages_domain_verification_enabled: false)
          allow(service).to receive(:notification_service) { notification_service }
        end

        it 'skips email notifications' do
          expect(notification_service).not_to receive(:pages_domain_enabled)

          service.execute
        end
      end
    end

    context 'no verification code' do
      let(:domain) { create(:pages_domain) }

      it 'returns an error' do
        domain.verification_code = ''

        disallow_resolver!

        expect(service.execute).to eq(status: :error, message: "No verification code set for #{domain.domain}")
      end
    end

    context 'pages domain verification is disabled' do
      let(:domain) { create(:pages_domain, :disabled) }

      before do
        stub_application_setting(pages_domain_verification_enabled: false)
      end

      it 'extends domain validity by unconditionally reverifying' do
        disallow_resolver!

        service.execute

        expect(domain).to be_verified
        expect(domain).to be_enabled
      end

      it 'does not shorten any grace period' do
        grace = Time.current + 1.year
        domain.update!(enabled_until: grace)
        disallow_resolver!

        service.execute

        expect(domain.enabled_until).to be_like_time(grace)
      end
    end
  end

  def disallow_resolver!
    expect(Resolv::DNS).not_to receive(:open)
  end
end


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

# This service passes Markdown content through our GFM rewriter classes
# which rewrite references to GitLab objects and uploads within the content
# based on their visibility by the `target_parent`.
class MarkdownContentRewriterService
  include Gitlab::Utils::StrongMemoize

  REWRITERS = [Gitlab::Gfm::ReferenceRewriter, Gitlab::Gfm::UploadsRewriter].freeze

  def initialize(current_user, object, field, source_parent, target_parent)
    @current_user = current_user
    @source_parent = source_parent
    @target_parent = target_parent
    @object = object
    @field = field

    validate_parameters!

    @content = object[field].dup.presence
    @html_field = object.cached_markdown_fields.html_field(field)
    @content_html = object.cached_html_for(field)

    @rewriters =
      REWRITERS.map do |rewriter_class|
        rewriter_class.new(@content, content_html, source_parent, current_user)
      end

    @result = {
      field => nil,
      html_field => nil
    }.with_indifferent_access
  end

  def execute
    return result unless content

    unless safe_to_copy_markdown?
      rewriters.each do |rewriter|
        rewriter.rewrite(target_parent)
      end
    end

    result[field] = content
    result[html_field] = content_html if safe_to_copy_markdown?
    result[:skip_markdown_cache_validation] = safe_to_copy_markdown?

    result
  end

  def safe_to_copy_markdown?
    strong_memoize(:safe_to_copy_markdown) do
      rewriters.none?(&:needs_rewrite?)
    end
  end

  private

  def validate_parameters!
    # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39654#note_399095117
    raise ArgumentError, 'The rewriter classes require that `source_parent` is a `Project`' \
      unless source_parent.is_a?(Project)

    if object.cached_markdown_fields[field].nil?
      raise ArgumentError, 'The `field` attribute does not contain cached markdown'
    end
  end

  attr_reader :current_user, :content, :source_parent,
              :target_parent, :rewriters, :content_html,
              :field, :html_field, :object, :result
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe MarkdownContentRewriterService, feature_category: :team_planning do
  let_it_be(:user) { create(:user) }
  let_it_be(:source_parent) { create(:project, :public) }
  let_it_be(:target_parent) { create(:project, :public) }

  let(:content) { 'My content' }
  let(:issue) { create(:issue, project: source_parent, description: content) }

  describe '#initialize' do
    it 'raises an error if source_parent is not a Project' do
      expect do
        described_class.new(user, issue, :description, create(:group), target_parent)
      end.to raise_error(ArgumentError, 'The rewriter classes require that `source_parent` is a `Project`')
    end

    it 'raises an error if field does not have cached markdown' do
      expect do
        described_class.new(user, issue, :author, source_parent, target_parent)
      end.to raise_error(ArgumentError, 'The `field` attribute does not contain cached markdown')
    end
  end

  describe '#execute' do
    subject { described_class.new(user, issue, :description, source_parent, target_parent).execute }

    context 'when content does not need a rewrite' do
      it 'returns original content and cached html' do
        expect(subject).to eq({
          'description' => issue.description,
          'description_html' => issue.description_html,
          'skip_markdown_cache_validation' => true
        })
      end
    end

    context 'when content needs a rewrite' do
      it 'calls the rewriter classes successfully', :aggregate_failures do
        described_class::REWRITERS.each do |rewriter_class|
          service = double

          allow(service).to receive(:needs_rewrite?).and_return(true)

          expect(service).to receive(:rewrite).with(target_parent)
          expect(rewriter_class).to receive(:new).and_return(service)
        end

        subject
      end
    end

    # Perform simple integration-style tests for each rewriter class.
    # to prove they run correctly.
    context 'when content has references' do
      let_it_be(:issue_to_reference) { create(:issue, project: source_parent) }

      let(:content) { "See ##{issue_to_reference.iid}" }

      it 'rewrites content' do
        expect(subject).to eq({
          'description' => "See #{source_parent.full_path}##{issue_to_reference.iid}",
          'description_html' => nil,
          'skip_markdown_cache_validation' => false
        })
      end
    end

    context 'when content contains an upload' do
      let(:image_uploader) { build(:file_uploader, project: source_parent) }
      let(:content) { "Text and #{image_uploader.markdown_link}" }

      it 'rewrites content' do
        new_content = subject

        expect(new_content[:description]).not_to eq(content)
        expect(new_content[:description].length).to eq(content.length)
        expect(new_content[1]).to eq(nil)
      end
    end
  end

  describe '#safe_to_copy_markdown?' do
    subject do
      rewriter = described_class.new(user, issue, :description, source_parent, target_parent)
      rewriter.safe_to_copy_markdown?
    end

    context 'when content has references' do
      let(:milestone) { create(:milestone, project: source_parent) }
      let(:content) { "Description that references #{milestone.to_reference}" }

      it { is_expected.to eq(false) }
    end

    context 'when content has uploaded file references' do
      let(:image_uploader) { build(:file_uploader, project: source_parent) }
      let(:content) { "Text and #{image_uploader.markdown_link}" }

      it { is_expected.to eq(false) }
    end

    context 'when content does not have references or uploads' do
      let(:content) { "simples text with ```code```" }

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


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

class SearchService
  include Gitlab::Allowable
  include Gitlab::Utils::StrongMemoize

  DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE
  MAX_PER_PAGE = 200

  attr_reader :params

  def initialize(current_user, params = {})
    @current_user = current_user
    @params = Gitlab::Search::Params.new(params, detect_abuse: true)
  end

  # rubocop: disable CodeReuse/ActiveRecord
  def project
    strong_memoize(:project) do
      if params[:project_id].present? && valid_request?
        the_project = Project.find_by(id: params[:project_id])
        can?(current_user, :read_project, the_project) ? the_project : nil
      end
    end
  end
  # rubocop: enable CodeReuse/ActiveRecord

  # rubocop: disable CodeReuse/ActiveRecord
  def group
    strong_memoize(:group) do
      if params[:group_id].present? && valid_request?
        the_group = Group.find_by(id: params[:group_id])
        can?(current_user, :read_group, the_group) ? the_group : nil
      end
    end
  end
  # rubocop: enable CodeReuse/ActiveRecord

  def projects
    # overridden in EE
  end

  def global_search?
    project.blank? && group.blank?
  end

  def search_type
    'basic'
  end

  def show_snippets?
    strong_memoize(:show_snippets) do
      params[:snippets] == 'true'
    end
  end

  delegate :scope, to: :search_service
  delegate :valid_terms_count?, :valid_query_length?, to: :params

  def search_results
    strong_memoize(:search_results) do
      abuse_detected? ? Gitlab::EmptySearchResults.new : search_service.execute
    end
  end

  def search_objects(preload_method = nil)
    @search_objects ||= redact_unauthorized_results(
      search_results.objects(scope, page: page, per_page: per_page, preload_method: preload_method)
    )
  end

  def search_highlight
    search_results.highlight_map(scope)
  end

  def search_aggregations
    search_results.aggregations(scope)
  end

  def abuse_detected?
    strong_memoize(:abuse_detected) do
      params.abusive?
    end
  end

  def abuse_messages
    return [] unless params.abusive?

    params.abuse_detection.errors.full_messages
  end

  def valid_request?
    strong_memoize(:valid_request) do
      params.valid?
    end
  end

  def level
    @level ||=
      if project
        'project'
      elsif group
        'group'
      else
        'global'
      end
  end

  def global_search_enabled_for_scope?
    return false if show_snippets? && Feature.disabled?(:global_search_snippet_titles_tab, current_user, type: :ops)

    case params[:scope]
    when 'blobs'
      Feature.enabled?(:global_search_code_tab, current_user, type: :ops)
    when 'commits'
      Feature.enabled?(:global_search_commits_tab, current_user, type: :ops)
    when 'issues'
      Feature.enabled?(:global_search_issues_tab, current_user, type: :ops)
    when 'merge_requests'
      Feature.enabled?(:global_search_merge_requests_tab, current_user, type: :ops)
    when 'snippet_titles'
      Feature.enabled?(:global_search_snippet_titles_tab, current_user, type: :ops)
    when 'wiki_blobs'
      Feature.enabled?(:global_search_wiki_tab, current_user, type: :ops)
    when 'users'
      Feature.enabled?(:global_search_users_tab, current_user, type: :ops)
    else
      true
    end
  end

  private

  def page
    [1, params[:page].to_i].max
  end

  def per_page
    per_page_param = params[:per_page].to_i

    return DEFAULT_PER_PAGE unless per_page_param > 0

    [MAX_PER_PAGE, per_page_param].min
  end

  def visible_result?(object)
    return true unless object.respond_to?(:to_ability_name) && DeclarativePolicy.has_policy?(object)

    Ability.allowed?(current_user, :"read_#{object.to_ability_name}", object)
  end

  def redact_unauthorized_results(results_collection)
    redacted_results = results_collection.reject { |object| visible_result?(object) }

    if redacted_results.any?
      redacted_log = redacted_results.each_with_object({}) do |object, memo|
        memo[object.id] = { ability: :"read_#{object.to_ability_name}", id: object.id, class_name: object.class.name }
      end

      log_redacted_search_results(redacted_log.values)

      return results_collection.id_not_in(redacted_log.keys) if results_collection.is_a?(ActiveRecord::Relation)
    end

    return results_collection if results_collection.is_a?(ActiveRecord::Relation)

    permitted_results = results_collection - redacted_results

    Kaminari.paginate_array(
      permitted_results,
      total_count: results_collection.total_count,
      limit: results_collection.limit_value,
      offset: results_collection.offset_value
    )
  end

  def log_redacted_search_results(filtered_results)
    logger.error(message: "redacted_search_results", filtered: filtered_results, current_user_id: current_user&.id, query: params[:search])
  end

  def logger
    @logger ||= ::Gitlab::RedactedSearchResultsLogger.build
  end

  def search_service
    @search_service ||=
      if project
        Search::ProjectService.new(current_user, project, params)
      elsif show_snippets?
        Search::SnippetService.new(current_user, params)
      elsif group
        Search::GroupService.new(current_user, group, params)
      else
        Search::GlobalService.new(current_user, params)
      end
  end

  attr_reader :current_user
end

SearchService.prepend_mod_with('SearchService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe SearchService, feature_category: :global_search do
  let_it_be(:user) { create(:user) }

  let_it_be(:accessible_group) { create(:group, :private) }
  let_it_be(:inaccessible_group) { create(:group, :private) }
  let_it_be(:group_member) { create(:group_member, group: accessible_group, user: user) }

  let_it_be(:accessible_project) { create(:project, :repository, :private, name: 'accessible_project') }
  let_it_be(:note) { create(:note_on_issue, project: accessible_project) }

  let_it_be(:inaccessible_project) { create(:project, :repository, :private, name: 'inaccessible_project') }

  let(:snippet) { create(:snippet, author: user) }
  let(:group_project) { create(:project, group: accessible_group, name: 'group_project') }
  let(:public_project) { create(:project, :public, name: 'public_project') }

  let(:page) { 1 }
  let(:per_page) { described_class::DEFAULT_PER_PAGE }
  let(:valid_search) { "what is love?" }

  subject(:search_service) { described_class.new(user, search: search, scope: scope, page: page, per_page: per_page) }

  before do
    accessible_project.add_maintainer(user)
  end

  describe '#project' do
    context 'when the project is accessible' do
      it 'returns the project' do
        project = described_class.new(user, project_id: accessible_project.id, search: valid_search).project

        expect(project).to eq accessible_project
      end

      it 'returns the project for guests' do
        search_project = create :project
        search_project.add_guest(user)

        project = described_class.new(user, project_id: search_project.id, search: valid_search).project

        expect(project).to eq search_project
      end
    end

    context 'when the project is not accessible' do
      it 'returns nil' do
        project = described_class.new(user, project_id: inaccessible_project.id, search: valid_search).project

        expect(project).to be_nil
      end
    end

    context 'when there is no project_id' do
      it 'returns nil' do
        project = described_class.new(user, search: valid_search).project

        expect(project).to be_nil
      end
    end
  end

  describe '#group' do
    context 'when the group is accessible' do
      it 'returns the group' do
        group = described_class.new(user, group_id: accessible_group.id, search: valid_search).group

        expect(group).to eq accessible_group
      end
    end

    context 'when the group is not accessible' do
      it 'returns nil' do
        group = described_class.new(user, group_id: inaccessible_group.id, search: valid_search).group

        expect(group).to be_nil
      end
    end

    context 'when there is no group_id' do
      it 'returns nil' do
        group = described_class.new(user, search: valid_search).group

        expect(group).to be_nil
      end
    end
  end

  describe '#search_type' do
    subject { described_class.new(user, search: valid_search).search_type }

    it { is_expected.to eq('basic') }
  end

  describe '#show_snippets?' do
    context 'when :snippets is \'true\'' do
      it 'returns true' do
        show_snippets = described_class.new(user, snippets: 'true').show_snippets?

        expect(show_snippets).to be_truthy
      end
    end

    context 'when :snippets is not \'true\'' do
      it 'returns false' do
        show_snippets = described_class.new(user, snippets: 'tru').show_snippets?

        expect(show_snippets).to be_falsey
      end
    end

    context 'when :snippets is missing' do
      it 'returns false' do
        show_snippets = described_class.new(user).show_snippets?

        expect(show_snippets).to be_falsey
      end
    end
  end

  describe '#scope' do
    context 'with accessible project_id' do
      context 'and allowed scope' do
        it 'returns the specified scope' do
          scope = described_class.new(user, project_id: accessible_project.id, scope: 'notes', search: valid_search).scope

          expect(scope).to eq 'notes'
        end
      end

      context 'and disallowed scope' do
        it 'returns the default scope' do
          scope = described_class.new(user, project_id: accessible_project.id, scope: 'projects', search: valid_search).scope

          expect(scope).to eq 'blobs'
        end
      end

      context 'and no scope' do
        it 'returns the default scope' do
          scope = described_class.new(user, project_id: accessible_project.id, search: valid_search).scope

          expect(scope).to eq 'blobs'
        end
      end
    end

    context 'with \'true\' snippets' do
      context 'and allowed scope' do
        it 'returns the specified scope' do
          scope = described_class.new(user, snippets: 'true', scope: 'snippet_titles').scope

          expect(scope).to eq 'snippet_titles'
        end
      end

      context 'and disallowed scope' do
        it 'returns the default scope' do
          scope = described_class.new(user, snippets: 'true', scope: 'projects').scope

          expect(scope).to eq 'snippet_titles'
        end
      end

      context 'and no scope' do
        it 'returns the default scope' do
          scope = described_class.new(user, snippets: 'true').scope

          expect(scope).to eq 'snippet_titles'
        end
      end
    end

    context 'with no project_id, no snippets' do
      context 'and allowed scope' do
        it 'returns the specified scope' do
          scope = described_class.new(user, scope: 'issues').scope

          expect(scope).to eq 'issues'
        end
      end

      context 'and disallowed scope' do
        it 'returns the default scope' do
          scope = described_class.new(user, scope: 'blobs').scope

          expect(scope).to eq 'projects'
        end
      end

      context 'and no scope' do
        it 'returns the default scope' do
          scope = described_class.new(user).scope

          expect(scope).to eq 'projects'
        end
      end
    end
  end

  describe '#search_results' do
    context 'with accessible project_id' do
      it 'returns an instance of Gitlab::ProjectSearchResults' do
        search_results = described_class.new(
          user,
          project_id: accessible_project.id,
          scope: 'notes',
          search: note.note).search_results

        expect(search_results).to be_a Gitlab::ProjectSearchResults
      end
    end

    context 'with accessible project_id and \'true\' snippets' do
      it 'returns an instance of Gitlab::ProjectSearchResults' do
        search_results = described_class.new(
          user,
          project_id: accessible_project.id,
          snippets: 'true',
          scope: 'notes',
          search: note.note).search_results

        expect(search_results).to be_a Gitlab::ProjectSearchResults
      end
    end

    context 'with \'true\' snippets' do
      it 'returns an instance of Gitlab::SnippetSearchResults' do
        search_results = described_class.new(
          user,
          snippets: 'true',
          search: snippet.title).search_results

        expect(search_results).to be_a Gitlab::SnippetSearchResults
      end
    end

    context 'with no project_id and no snippets' do
      it 'returns an instance of Gitlab::SearchResults' do
        search_results = described_class.new(
          user,
          search: public_project.name).search_results

        expect(search_results).to be_a Gitlab::SearchResults
      end
    end
  end

  describe '#search_objects' do
    let(:search) { '' }
    let(:scope) { nil }

    describe 'per_page: parameter' do
      context 'when nil' do
        let(:per_page) { nil }

        it "defaults to #{described_class::DEFAULT_PER_PAGE}" do
          expect_any_instance_of(Gitlab::SearchResults)
            .to receive(:objects)
            .with(anything, hash_including(per_page: described_class::DEFAULT_PER_PAGE))
            .and_call_original

          subject.search_objects
        end
      end

      context 'when empty string' do
        let(:per_page) { '' }

        it "defaults to #{described_class::DEFAULT_PER_PAGE}" do
          expect_any_instance_of(Gitlab::SearchResults)
            .to receive(:objects)
            .with(anything, hash_including(per_page: described_class::DEFAULT_PER_PAGE))
            .and_call_original

          subject.search_objects
        end
      end

      context 'when negative' do
        let(:per_page) { '-1' }

        it "defaults to #{described_class::DEFAULT_PER_PAGE}" do
          expect_any_instance_of(Gitlab::SearchResults)
            .to receive(:objects)
            .with(anything, hash_including(per_page: described_class::DEFAULT_PER_PAGE))
            .and_call_original

          subject.search_objects
        end
      end

      context 'when present' do
        let(:per_page) { '50' }

        it "converts to integer and passes to search results" do
          expect_any_instance_of(Gitlab::SearchResults)
            .to receive(:objects)
            .with(anything, hash_including(per_page: 50))
            .and_call_original

          subject.search_objects
        end
      end

      context "when greater than #{described_class::MAX_PER_PAGE}" do
        let(:per_page) { described_class::MAX_PER_PAGE + 1 }

        it "passes #{described_class::MAX_PER_PAGE}" do
          expect_any_instance_of(Gitlab::SearchResults)
            .to receive(:objects)
            .with(anything, hash_including(per_page: described_class::MAX_PER_PAGE))
            .and_call_original

          subject.search_objects
        end
      end
    end

    describe 'page: parameter' do
      context 'when < 1' do
        let(:page) { 0 }

        it "defaults to 1" do
          expect_any_instance_of(Gitlab::SearchResults)
            .to receive(:objects)
            .with(anything, hash_including(page: 1))
            .and_call_original

          subject.search_objects
        end
      end

      context 'when nil' do
        let(:page) { nil }

        it "defaults to 1" do
          expect_any_instance_of(Gitlab::SearchResults)
            .to receive(:objects)
            .with(anything, hash_including(page: 1))
            .and_call_original

          subject.search_objects
        end
      end
    end

    context 'with accessible project_id' do
      it 'returns objects in the project' do
        search_objects = described_class.new(
          user,
          project_id: accessible_project.id,
          scope: 'notes',
          search: note.note).search_objects

        expect(search_objects.first).to eq note
      end
    end

    context 'with accessible project_id and \'true\' snippets' do
      it 'returns objects in the project' do
        search_objects = described_class.new(
          user,
          project_id: accessible_project.id,
          snippets: 'true',
          scope: 'notes',
          search: note.note).search_objects

        expect(search_objects.first).to eq note
      end
    end

    context 'with \'true\' snippets' do
      it 'returns objects in snippets' do
        search_objects = described_class.new(
          user,
          snippets: 'true',
          search: snippet.title).search_objects

        expect(search_objects.first).to eq snippet
      end
    end

    context 'with accessible group_id' do
      it 'returns objects in the group' do
        search_objects = described_class.new(
          user,
          group_id: accessible_group.id,
          search: group_project.name).search_objects

        expect(search_objects.first).to eq group_project
      end
    end

    context 'with no project_id, group_id or snippets' do
      it 'returns objects in global' do
        search_objects = described_class.new(
          user,
          search: public_project.name).search_objects

        expect(search_objects.first).to eq public_project
      end
    end

    it_behaves_like 'a redacted search results'
  end

  describe '#valid_request?' do
    let(:scope) { 'issues' }
    let(:search) { 'foobar' }
    let(:params) { instance_double(Gitlab::Search::Params) }

    before do
      allow(Gitlab::Search::Params).to receive(:new).and_return(params)
      allow(params).to receive(:valid?).and_return double(:valid?)
    end

    it 'is the return value of params.valid?' do
      expect(subject.valid_request?).to eq(params.valid?)
    end
  end

  describe '#abuse_messages' do
    let(:scope) { 'issues' }
    let(:search) { 'foobar' }
    let(:params) { instance_double(Gitlab::Search::Params) }

    before do
      allow(Gitlab::Search::Params).to receive(:new).and_return(params)
    end

    it 'returns an empty array when not abusive' do
      allow(params).to receive(:abusive?).and_return false
      expect(subject.abuse_messages).to match_array([])
    end

    it 'calls on abuse_detection.errors.full_messages when abusive' do
      allow(params).to receive(:abusive?).and_return true
      expect(params).to receive_message_chain(:abuse_detection, :errors, :full_messages)
      subject.abuse_messages
    end
  end

  describe 'abusive search handling' do
    subject { described_class.new(user, raw_params) }

    let(:raw_params) { { search: search, scope: scope } }
    let(:search) { 'foobar' }

    let(:search_service) { double(:search_service) }

    before do
      expect(Gitlab::Search::Params).to receive(:new)
        .with(raw_params, detect_abuse: true).and_call_original

      allow(subject).to receive(:search_service).and_return search_service
    end

    context 'a search is abusive' do
      let(:scope) { '1;drop%20table' }

      it 'does NOT execute search service' do
        expect(search_service).not_to receive(:execute)
        subject.search_results
      end
    end

    context 'a search is NOT abusive' do
      let(:scope) { 'issues' }

      it 'executes search service' do
        expect(search_service).to receive(:execute)
        subject.search_results
      end
    end
  end

  describe '.global_search_enabled_for_scope?' do
    using RSpec::Parameterized::TableSyntax
    let(:search) { 'foobar' }

    where(:scope, :feature_flag, :enabled, :expected) do
      'blobs'          | :global_search_code_tab           | false | false
      'blobs'          | :global_search_code_tab           | true  | true
      'commits'        | :global_search_commits_tab        | false | false
      'commits'        | :global_search_commits_tab        | true  | true
      'issues'         | :global_search_issues_tab         | false | false
      'issues'         | :global_search_issues_tab         | true  | true
      'merge_requests' | :global_search_merge_requests_tab | false | false
      'merge_requests' | :global_search_merge_requests_tab | true  | true
      'snippet_titles' | :global_search_snippet_titles_tab | false | false
      'snippet_titles' | :global_search_snippet_titles_tab | true  | true
      'wiki_blobs'     | :global_search_wiki_tab           | false | false
      'wiki_blobs'     | :global_search_wiki_tab           | true  | true
      'users'          | :global_search_users_tab          | false | false
      'users'          | :global_search_users_tab          | true  | true
      'random'         | :random                           | nil   | true
    end

    with_them do
      it 'returns false when feature_flag is not enabled and returns true when feature_flag is enabled' do
        stub_feature_flags(feature_flag => enabled)
        expect(subject.global_search_enabled_for_scope?).to eq expected
      end
    end

    context 'when snippet search is enabled' do
      let(:scope) { 'snippet_titles' }

      before do
        allow(described_class).to receive(:show_snippets?).and_return(true)
      end

      it 'returns false when feature_flag is not enabled' do
        stub_feature_flags(global_search_snippet_titles_tab: false)

        expect(subject.global_search_enabled_for_scope?).to eq false
      end

      it 'returns true when feature_flag is enabled' do
        stub_feature_flags(global_search_snippet_titles_tab: true)

        expect(subject.global_search_enabled_for_scope?).to eq true
      end
    end
  end
end


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

class SystemHooksService
  def execute_hooks_for(model, event)
    data = build_event_data(model, event)

    model.run_after_commit_or_now do
      SystemHooksService.new.execute_hooks(data)
    end
  end

  def execute_hooks(data, hooks_scope = :all)
    SystemHook.executable.hooks_for(hooks_scope).find_each do |hook|
      hook.async_execute(data, 'system_hooks')
    end

    Gitlab::FileHook.execute_all_async(data)
  end

  private

  def build_event_data(model, event)
    builder_class = case model
                    when GroupMember
                      Gitlab::HookData::GroupMemberBuilder
                    when Group
                      Gitlab::HookData::GroupBuilder
                    when ProjectMember
                      Gitlab::HookData::ProjectMemberBuilder
                    when User
                      Gitlab::HookData::UserBuilder
                    when Project
                      Gitlab::HookData::ProjectBuilder
                    when Key
                      Gitlab::HookData::KeyBuilder
                    end

    builder_class.new(model).build(event)
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe SystemHooksService, feature_category: :webhooks do
  describe '#execute_hooks_for' do
    let_it_be(:user) { create(:user) }
    let_it_be(:group) { create(:group) }
    let_it_be(:project) { create(:project) }
    let_it_be(:group_member) { create(:group_member, source: group, user: user) }
    let_it_be(:project_member) { create(:project_member, source: project, user: user) }
    let_it_be(:key) { create(:key, user: user) }
    let_it_be(:deploy_key) { create(:key) }

    let(:event) { :create }

    using RSpec::Parameterized::TableSyntax

    where(:model_name, :builder_class) do
      :group_member | Gitlab::HookData::GroupMemberBuilder
      :group | Gitlab::HookData::GroupBuilder
      :project_member | Gitlab::HookData::ProjectMemberBuilder
      :user | Gitlab::HookData::UserBuilder
      :project | Gitlab::HookData::ProjectBuilder
      :key | Gitlab::HookData::KeyBuilder
      :deploy_key | Gitlab::HookData::KeyBuilder
    end

    with_them do
      it 'builds the data with the relevant builder class and then calls #execute_hooks with the obtained data' do
        data = double
        model = public_send(model_name)

        expect_next_instance_of(builder_class, model) do |builder|
          expect(builder).to receive(:build).with(event).and_return(data)
        end

        service = described_class.new

        expect_next_instance_of(SystemHooksService) do |system_hook_service|
          expect(system_hook_service).to receive(:execute_hooks).with(data)
        end

        service.execute_hooks_for(model, event)
      end
    end
  end

  describe '#execute_hooks' do
    let(:data) { { key: :value } }

    subject { described_class.new.execute_hooks(data) }

    it 'executes system hooks with the given data' do
      hook = create(:system_hook)

      allow(SystemHook).to receive_message_chain(:hooks_for, :find_each).and_yield(hook)

      expect(hook).to receive(:async_execute).with(data, 'system_hooks')

      subject
    end

    it 'executes FileHook with the given data' do
      expect(Gitlab::FileHook).to receive(:execute_all_async).with(data)

      subject
    end
  end
end


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

require 'securerandom'

# Compare 2 refs for one repo or between repositories
# and return Compare object that responds to commits and diffs
class CompareService
  attr_reader :start_project, :start_ref_name

  def initialize(new_start_project, new_start_ref_name)
    @start_project = new_start_project
    @start_ref_name = new_start_ref_name
  end

  def execute(target_project, target_ref, base_sha: nil, straight: false)
    raw_compare = target_project.repository.compare_source_branch(target_ref, start_project.repository, start_ref_name, straight: straight)

    return unless raw_compare && raw_compare.base && raw_compare.head

    Compare.new(raw_compare, start_project, base_sha: base_sha, straight: straight)
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe CompareService, feature_category: :source_code_management do
  let(:project) { create(:project, :repository) }
  let(:user) { create(:user) }
  let(:service) { described_class.new(project, 'feature') }

  describe '#execute' do
    context 'compare with base, like feature...fix' do
      subject { service.execute(project, 'fix', straight: false) }

      it { expect(subject.diffs.size).to eq(1) }
    end

    context 'straight compare, like feature..fix' do
      subject { service.execute(project, 'fix', straight: true) }

      it { expect(subject.diffs.size).to eq(3) }
    end

    context 'compare with target branch that does not exist' do
      subject { service.execute(project, 'non-existent-ref') }

      it { expect(subject).to be_nil }
    end

    context 'compare with source branch that does not exist' do
      let(:service) { described_class.new(project, 'non-existent-branch') }

      subject { service.execute(project, 'non-existent-ref') }

      it { expect(subject).to be_nil }
    end
  end
end


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

class UserAgentDetailService
  def initialize(spammable:, perform_spam_check:)
    @spammable = spammable
    @perform_spam_check = perform_spam_check
  end

  def create
    spam_params = Gitlab::RequestContext.instance.spam_params
    if !perform_spam_check || spam_params&.user_agent.blank? || spam_params&.ip_address.blank?
      message = 'Skipped UserAgentDetail creation because necessary spam_params were not provided'
      return ServiceResponse.success(message: message)
    end

    spammable.create_user_agent_detail(user_agent: spam_params.user_agent, ip_address: spam_params.ip_address)
  end

  private

  attr_reader :spammable, :perform_spam_check
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe UserAgentDetailService, feature_category: :instance_resiliency do
  describe '#create', :request_store do
    let_it_be(:spammable) { create(:issue) }

    using RSpec::Parameterized::TableSyntax

    where(:perform_spam_check, :spam_params_present, :user_agent, :ip_address, :creates_user_agent_detail) do
      true  | true  | 'UA' | 'IP' | true
      true  | false | 'UA' | 'IP' | false
      false | true  | 'UA' | 'IP' | false
      true  | true  | ''   | 'IP' | false
      true  | true  | nil  | 'IP' | false
      true  | true  | 'UA' | ''   | false
      true  | true  | 'UA' | nil  | false
    end

    with_them do
      let(:spam_params) do
        instance_double('Spam::SpamParams', user_agent: user_agent, ip_address: ip_address) if spam_params_present
      end

      before do
        allow(Gitlab::RequestContext.instance).to receive(:spam_params).and_return(spam_params)
      end

      subject { described_class.new(spammable: spammable, perform_spam_check: perform_spam_check).create } # rubocop:disable Rails/SaveBang

      it 'creates a user agent detail when expected' do
        if creates_user_agent_detail
          expect { subject }.to change { UserAgentDetail.count }.by(1)
        else
          expect(subject).to be_a ServiceResponse
        end
      end
    end
  end
end


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

class X509CertificateRevokeService
  def execute(certificate)
    return unless certificate.revoked?

    certificate.x509_commit_signatures.update_all(verification_status: :unverified)
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe X509CertificateRevokeService, feature_category: :system_access do
  describe '#execute' do
    let(:service) { described_class.new }
    let!(:x509_signature_1) { create(:x509_commit_signature, x509_certificate: x509_certificate, verification_status: :verified) }
    let!(:x509_signature_2) { create(:x509_commit_signature, x509_certificate: x509_certificate, verification_status: :verified) }

    context 'for revoked certificates' do
      let(:x509_certificate) { create(:x509_certificate, certificate_status: :revoked) }

      it 'update all commit signatures' do
        expect do
          service.execute(x509_certificate)

          x509_signature_1.reload
          x509_signature_2.reload
        end
          .to change(x509_signature_1, :verification_status).from('verified').to('unverified')
          .and change(x509_signature_2, :verification_status).from('verified').to('unverified')
      end
    end

    context 'for good certificates' do
      let(:x509_certificate) { create(:x509_certificate) }

      it 'do not update any commit signature' do
        expect do
          service.execute(x509_certificate)

          x509_signature_1.reload
          x509_signature_2.reload
        end
          .to not_change(x509_signature_1, :verification_status)
          .and not_change(x509_signature_2, :verification_status)
      end
    end
  end
end


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

# EventCreateService class
#
# Used for creating events feed on dashboard after certain user action
#
# Ex.
#   EventCreateService.new.new_issue(issue, current_user)
#
class EventCreateService
  IllegalActionError = Class.new(StandardError)

  DEGIGN_EVENT_LABEL = 'usage_activity_by_stage_monthly.create.action_monthly_active_users_design_management'
  MR_EVENT_LABEL = 'usage_activity_by_stage_monthly.create.merge_requests_users'
  MR_EVENT_PROPERTY = 'merge_request_action'

  def open_issue(issue, current_user)
    create_record_event(issue, current_user, :created)
  end

  def close_issue(issue, current_user)
    create_record_event(issue, current_user, :closed)
  end

  def reopen_issue(issue, current_user)
    create_record_event(issue, current_user, :reopened)
  end

  def open_mr(merge_request, current_user)
    create_record_event(merge_request, current_user, :created).tap do
      Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:merge_request_action, values: current_user.id)
      track_snowplow_event(
        action: :created,
        project: merge_request.project,
        user: current_user,
        label: MR_EVENT_LABEL,
        property: MR_EVENT_PROPERTY
      )
    end
  end

  def close_mr(merge_request, current_user)
    create_record_event(merge_request, current_user, :closed).tap do
      Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:merge_request_action, values: current_user.id)
      track_snowplow_event(
        action: :closed,
        project: merge_request.project,
        user: current_user,
        label: MR_EVENT_LABEL,
        property: MR_EVENT_PROPERTY
      )
    end
  end

  def reopen_mr(merge_request, current_user)
    create_record_event(merge_request, current_user, :reopened)
  end

  def merge_mr(merge_request, current_user)
    create_record_event(merge_request, current_user, :merged).tap do
      Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:merge_request_action, values: current_user.id)
      track_snowplow_event(
        action: :merged,
        project: merge_request.project,
        user: current_user,
        label: MR_EVENT_LABEL,
        property: MR_EVENT_PROPERTY
      )
    end
  end

  def open_milestone(milestone, current_user)
    create_record_event(milestone, current_user, :created)
  end

  def close_milestone(milestone, current_user)
    create_record_event(milestone, current_user, :closed)
  end

  def reopen_milestone(milestone, current_user)
    create_record_event(milestone, current_user, :reopened)
  end

  def destroy_milestone(milestone, current_user)
    create_record_event(milestone, current_user, :destroyed)
  end

  def leave_note(note, current_user)
    create_record_event(note, current_user, :commented).tap do
      if note.is_a?(DiffNote) && note.for_merge_request?
        Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:merge_request_action, values: current_user.id)
        track_snowplow_event(
          action: :commented,
          project: note.project,
          user: current_user,
          label: MR_EVENT_LABEL,
          property: MR_EVENT_PROPERTY
        )
      end
    end
  end

  def join_project(project, current_user)
    create_event(project, current_user, :joined)
  end

  def leave_project(project, current_user)
    create_event(project, current_user, :left)
  end

  def expired_leave_project(project, current_user)
    create_event(project, current_user, :expired)
  end

  def create_project(project, current_user)
    create_event(project, current_user, :created)
  end

  def push(project, current_user, push_data)
    create_push_event(PushEventPayloadService, project, current_user, push_data)
  end

  def bulk_push(project, current_user, push_data)
    create_push_event(BulkPushEventPayloadService, project, current_user, push_data)
  end

  def save_designs(current_user, create: [], update: [])
    records = create.zip([:created].cycle) + update.zip([:updated].cycle)
    return [] if records.empty?

    event_meta = { user: current_user, label: DEGIGN_EVENT_LABEL, property: :design_action }
    track_snowplow_event(action: :create, project: create.first.project, **event_meta) if create.any?

    track_snowplow_event(action: :update, project: update.first.project, **event_meta) if update.any?

    inserted_events = create_record_events(records, current_user)

    Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:design_action, values: current_user.id)
    Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:git_write_action, values: current_user.id)

    inserted_events
  end

  def destroy_designs(designs, current_user)
    return [] unless designs.present?

    track_snowplow_event(
      action: :destroy,
      project: designs.first.project,
      user: current_user,
      label: DEGIGN_EVENT_LABEL,
      property: :design_action
    )

    inserted_events = create_record_events(designs.zip([:destroyed].cycle), current_user)

    Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:design_action, values: current_user.id)
    Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:git_write_action, values: current_user.id)

    inserted_events
  end

  # Create a new wiki page event
  #
  # @param [WikiPage::Meta] wiki_page_meta The event target
  # @param [User] author The event author
  # @param [Symbol] action One of the Event::WIKI_ACTIONS
  # @param [String] fingerprint The de-duplication fingerprint
  #
  # The fingerprint, if provided, should be sufficient to find duplicate events.
  # Suitable values would be, for example, the current page SHA.
  #
  # @return [Event] the event
  def wiki_event(wiki_page_meta, author, action, fingerprint)
    raise IllegalActionError, action unless Event::WIKI_ACTIONS.include?(action)

    Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:wiki_action, values: author.id)
    Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:git_write_action, values: author.id)

    duplicate = Event.for_wiki_meta(wiki_page_meta).for_fingerprint(fingerprint).first
    return duplicate if duplicate.present?

    create_record_event(wiki_page_meta, author, action, fingerprint.presence)
  end

  def approve_mr(merge_request, current_user)
    create_record_event(merge_request, current_user, :approved)
  end

  private

  def create_record_event(record, current_user, status, fingerprint = nil)
    create_event(
      record.resource_parent,
      current_user,
      status,
      fingerprint: fingerprint,
      target_id: record.id,
      target_type: record.class.name
    )
  end

  # If creating several events, this method will insert them all in a single
  # statement
  #
  # @param [[Eventable, Symbol, String]] a list of tuples of records, a valid status, and fingerprint
  # @param [User] the author of the event
  def create_record_events(tuples, current_user)
    base_attrs = {
      created_at: Time.now.utc,
      updated_at: Time.now.utc,
      author_id: current_user.id
    }

    attribute_sets = tuples.map do |record, status, fingerprint|
      action = Event.actions[status]
      raise IllegalActionError, "#{status} is not a valid status" if action.nil?

      parent_attrs(record.resource_parent)
        .merge(base_attrs)
        .merge(action: action, fingerprint: fingerprint, target_id: record.id, target_type: record.class.name)
    end

    Event.insert_all(attribute_sets, returning: %w[id])
  end

  def create_push_event(service_class, project, current_user, push_data)
    # We're using an explicit transaction here so that any errors that may occur
    # when creating push payload data will result in the event creation being
    # rolled back as well.
    event = Event.transaction do
      new_event = create_event(project, current_user, :pushed)

      service_class.new(new_event, push_data).execute

      new_event
    end

    Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:project_action, values: current_user.id)
    Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:git_write_action, values: current_user.id)

    namespace = project.namespace
    Gitlab::Tracking.event(
      self.class.to_s,
      :push,
      label: 'usage_activity_by_stage_monthly.create.action_monthly_active_users_project_repo',
      namespace: namespace,
      user: current_user,
      project: project,
      property: 'project_action',
      context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: 'project_action').to_context]
    )

    Users::LastPushEventService.new(current_user)
      .cache_last_push_event(event)

    Users::ActivityService.new(author: current_user, namespace: namespace, project: project).execute
  end

  def create_event(resource_parent, current_user, status, attributes = {})
    attributes.reverse_merge!(
      action: status,
      author_id: current_user.id
    )
    attributes.merge!(parent_attrs(resource_parent))

    if attributes[:fingerprint].present?
      Event.safe_find_or_create_by!(attributes)
    else
      Event.create!(attributes)
    end
  end

  def parent_attrs(resource_parent)
    resource_parent_attr = case resource_parent
                           when Project
                             :project_id
                           when Group
                             :group_id
                           end

    return {} unless resource_parent_attr

    { resource_parent_attr => resource_parent.id }
  end

  def track_snowplow_event(action:, project:, user:, label:, property:)
    Gitlab::Tracking.event(
      self.class.to_s,
      action.to_s,
      label: label,
      namespace: project.namespace,
      user: user,
      project: project,
      property: property.to_s,
      context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: property.to_s).to_context]
    )
  end
end

EventCreateService.prepend_mod_with('EventCreateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state, feature_category: :service_ping do
  include SnowplowHelpers

  let(:service) { described_class.new }
  let(:dates) { { start_date: Date.today.beginning_of_week, end_date: Date.today.next_week } }

  let_it_be(:user, reload: true) { create :user }
  let_it_be(:project) { create(:project) }

  shared_examples 'it records the event in the event counter' do
    specify do
      tracking_params = { event_names: event_action, **dates }

      expect { subject }
        .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) }
        .by(1)
    end
  end

  shared_examples 'it records a git write event' do
    specify do
      tracking_params = { event_names: 'git_write_action', **dates }

      expect { subject }
        .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) }
        .by(1)
    end
  end

  describe 'Issues' do
    describe '#open_issue' do
      let(:issue) { create(:issue) }

      it { expect(service.open_issue(issue, issue.author)).to be_truthy }

      it "creates new event" do
        expect { service.open_issue(issue, issue.author) }.to change { Event.count }
      end
    end

    describe '#close_issue' do
      let(:issue) { create(:issue) }

      it { expect(service.close_issue(issue, issue.author)).to be_truthy }

      it "creates new event" do
        expect { service.close_issue(issue, issue.author) }.to change { Event.count }
      end
    end

    describe '#reopen_issue' do
      let(:issue) { create(:issue) }

      it { expect(service.reopen_issue(issue, issue.author)).to be_truthy }

      it "creates new event" do
        expect { service.reopen_issue(issue, issue.author) }.to change { Event.count }
      end
    end
  end

  describe 'Merge Requests', :snowplow do
    describe '#open_mr' do
      subject(:open_mr) { service.open_mr(merge_request, merge_request.author) }

      let(:merge_request) { create(:merge_request) }

      it { expect(open_mr).to be_truthy }

      it "creates new event" do
        expect { open_mr }.to change { Event.count }
      end

      it_behaves_like "it records the event in the event counter" do
        let(:event_action) { :merge_request_action }
      end

      it_behaves_like 'Snowplow event tracking with RedisHLL context' do
        let(:category) { described_class.name }
        let(:action) { 'created' }
        let(:label) {  described_class::MR_EVENT_LABEL }
        let(:namespace) { project.namespace }
        let(:project) { merge_request.project }
        let(:user) { merge_request.author }
        let(:property) { described_class::MR_EVENT_PROPERTY }
        let(:context) do
          [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: property).to_context]
        end
      end
    end

    describe '#close_mr' do
      subject(:close_mr) { service.close_mr(merge_request, merge_request.author) }

      let(:merge_request) { create(:merge_request) }

      it { expect(close_mr).to be_truthy }

      it "creates new event" do
        expect { close_mr }.to change { Event.count }
      end

      it_behaves_like "it records the event in the event counter" do
        let(:event_action) { :merge_request_action }
      end

      it_behaves_like 'Snowplow event tracking with RedisHLL context' do
        let(:category) { described_class.name }
        let(:action) { 'closed' }
        let(:label) {  described_class::MR_EVENT_LABEL }
        let(:namespace) { project.namespace }
        let(:project) { merge_request.project }
        let(:user) { merge_request.author }
        let(:property) { described_class::MR_EVENT_PROPERTY }
        let(:context) do
          [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: property).to_context]
        end
      end
    end

    describe '#merge_mr' do
      subject(:merge_mr) { service.merge_mr(merge_request, merge_request.author) }

      let(:merge_request) { create(:merge_request) }

      it { expect(merge_mr).to be_truthy }

      it "creates new event" do
        expect { merge_mr }.to change { Event.count }
      end

      it_behaves_like "it records the event in the event counter" do
        let(:event_action) { :merge_request_action }
      end

      it_behaves_like 'Snowplow event tracking with RedisHLL context' do
        let(:category) { described_class.name }
        let(:action) { 'merged' }
        let(:label) { described_class::MR_EVENT_LABEL }
        let(:namespace) { project.namespace }
        let(:project) { merge_request.project }
        let(:user) { merge_request.author }
        let(:property) { described_class::MR_EVENT_PROPERTY }
        let(:context) do
          [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: property).to_context]
        end
      end
    end

    describe '#reopen_mr' do
      let(:merge_request) { create(:merge_request) }

      it { expect(service.reopen_mr(merge_request, merge_request.author)).to be_truthy }

      it "creates new event" do
        expect { service.reopen_mr(merge_request, merge_request.author) }.to change { Event.count }
      end
    end

    describe '#approve_mr' do
      let(:merge_request) { create(:merge_request) }

      it { expect(service.approve_mr(merge_request, user)).to be_truthy }

      it 'creates new event' do
        service.approve_mr(merge_request, user)

        change { Event.approved_action.where(target: merge_request).count }.by(1)
      end
    end
  end

  describe 'Milestone' do
    describe '#open_milestone' do
      let(:milestone) { create(:milestone) }

      it { expect(service.open_milestone(milestone, user)).to be_truthy }

      it "creates new event" do
        expect { service.open_milestone(milestone, user) }.to change { Event.count }
      end
    end

    describe '#close_mr' do
      let(:milestone) { create(:milestone) }

      it { expect(service.close_milestone(milestone, user)).to be_truthy }

      it "creates new event" do
        expect { service.close_milestone(milestone, user) }.to change { Event.count }
      end
    end

    describe '#destroy_mr' do
      let(:milestone) { create(:milestone) }

      it { expect(service.destroy_milestone(milestone, user)).to be_truthy }

      it "creates new event" do
        expect { service.destroy_milestone(milestone, user) }.to change { Event.count }
      end
    end
  end

  shared_examples_for 'service for creating a push event' do |service_class|
    it 'creates a new event' do
      expect { subject }.to change { Event.count }
    end

    it 'creates the push event payload' do
      expect(service_class).to receive(:new)
        .with(an_instance_of(PushEvent), push_data)
        .and_call_original

      subject
    end

    it 'updates user last activity' do
      expect { subject }.to change { user.last_activity_on }.to(Date.today)
    end

    it 'caches the last push event for the user' do
      expect_next_instance_of(Users::LastPushEventService) do |instance|
        expect(instance).to receive(:cache_last_push_event).with(an_instance_of(PushEvent))
      end

      subject
    end

    it 'does not create any event data when an error is raised' do
      payload_service = double(:service)

      allow(payload_service).to receive(:execute)
        .and_raise(RuntimeError)

      allow(service_class).to receive(:new)
        .and_return(payload_service)

      expect { subject }.to raise_error(RuntimeError)
      expect(Event.count).to eq(0)
      expect(PushEventPayload.count).to eq(0)
    end
  end

  describe '#wiki_event' do
    let_it_be(:user) { create(:user) }
    let_it_be(:wiki_page) { create(:wiki_page) }
    let_it_be(:meta) { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) }

    let(:fingerprint) { generate(:sha) }

    def create_event
      service.wiki_event(meta, user, action, fingerprint)
    end

    where(:action) { Event::WIKI_ACTIONS.map { |action| [action] } }

    with_them do
      subject { create_event }

      it 'creates the event' do
        expect(create_event).to have_attributes(
          wiki_page?: true,
          valid?: true,
          persisted?: true,
          action: action.to_s,
          wiki_page: wiki_page,
          author: user,
          fingerprint: fingerprint
        )
      end

      it 'is idempotent', :aggregate_failures do
        event = nil
        expect { event = create_event }.to change(Event, :count).by(1)
        duplicate = nil
        expect { duplicate = create_event }.not_to change(Event, :count)

        expect(duplicate).to eq(event)
      end

      it_behaves_like "it records the event in the event counter" do
        let(:event_action) { :wiki_action }
      end

      it_behaves_like "it records a git write event"
    end

    (Event.actions.keys - Event::WIKI_ACTIONS).each do |bad_action|
      context "The action is #{bad_action}" do
        let(:action) { bad_action }

        it 'raises an error' do
          expect { create_event }.to raise_error(described_class::IllegalActionError)
        end
      end
    end
  end

  describe '#push', :snowplow do
    let(:push_data) do
      {
        commits: [
          {
            id: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
            message: 'This is a commit'
          }
        ],
        before: '0000000000000000000000000000000000000000',
        after: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
        total_commits_count: 1,
        ref: 'refs/heads/my-branch'
      }
    end

    subject { service.push(project, user, push_data) }

    it_behaves_like 'service for creating a push event', PushEventPayloadService

    it_behaves_like "it records the event in the event counter" do
      let(:event_action) { :project_action }
    end

    it_behaves_like "it records a git write event"

    it_behaves_like 'Snowplow event tracking with RedisHLL context' do
      let(:category) { described_class.to_s }
      let(:action) { :push }
      let(:namespace) { project.namespace }
      let(:label) { 'usage_activity_by_stage_monthly.create.action_monthly_active_users_project_repo' }
      let(:property) { 'project_action' }
    end
  end

  describe '#bulk_push', :snowplow do
    let(:push_data) do
      {
        action: :created,
        ref_count: 4,
        ref_type: :branch
      }
    end

    subject { service.bulk_push(project, user, push_data) }

    it_behaves_like 'service for creating a push event', BulkPushEventPayloadService

    it_behaves_like "it records the event in the event counter" do
      let(:event_action) { :project_action }
    end

    it_behaves_like "it records a git write event"

    it_behaves_like 'Snowplow event tracking with RedisHLL context' do
      let(:category) { described_class.to_s }
      let(:action) { :push }
      let(:namespace) { project.namespace }
      let(:label) { 'usage_activity_by_stage_monthly.create.action_monthly_active_users_project_repo' }
      let(:property) { 'project_action' }
    end
  end

  describe 'Project' do
    describe '#join_project' do
      subject { service.join_project(project, user) }

      it { is_expected.to be_truthy }
      it { expect { subject }.to change { Event.count }.from(0).to(1) }
    end

    describe '#expired_leave_project' do
      subject { service.expired_leave_project(project, user) }

      it { is_expected.to be_truthy }
      it { expect { subject }.to change { Event.count }.from(0).to(1) }
    end
  end

  describe 'design events', :snowplow do
    let_it_be(:design) { create(:design, project: project) }
    let_it_be(:author) { user }

    before do
      allow(Gitlab::Tracking).to receive(:event) # rubocop:disable RSpec/ExpectGitlabTracking
    end

    describe '#save_designs' do
      let_it_be(:updated) { create_list(:design, 5) }
      let_it_be(:created) { create_list(:design, 3) }

      subject(:result) { service.save_designs(author, create: created, update: updated) }

      specify { expect { result }.to change { Event.count }.by(8) }

      # An addditional query due to event tracking
      specify { expect { result }.not_to exceed_query_limit(2) }

      it 'creates 3 created design events' do
        ids = result.pluck('id')
        events = Event.created_action.where(id: ids)

        expect(events.map(&:design)).to match_array(created)
      end

      it 'creates 5 created design events' do
        ids = result.pluck('id')
        events = Event.updated_action.where(id: ids)

        expect(events.map(&:design)).to match_array(updated)
      end

      it_behaves_like "it records the event in the event counter" do
        let(:event_action) { :design_action }
      end

      it_behaves_like "it records a git write event"

      describe 'Snowplow tracking' do
        let(:project) { design.project }
        let(:namespace) { project.namespace }
        let(:category) { described_class.name }
        let(:property) { :design_action.to_s }
        let(:label) { ::EventCreateService::DEGIGN_EVENT_LABEL }

        context 'for create event' do
          it_behaves_like 'Snowplow event tracking with RedisHLL context' do
            subject(:design_service) { service.save_designs(author, create: [design]) }

            let(:action) { 'create' }
          end
        end

        context 'for update event' do
          it_behaves_like 'Snowplow event tracking with RedisHLL context' do
            subject(:design_service) { service.save_designs(author, update: [design]) }

            let(:action) { 'update' }
          end
        end
      end
    end

    describe '#destroy_designs' do
      let_it_be(:designs) { create_list(:design, 5) }
      let_it_be(:author) { create(:user) }

      subject(:result) { service.destroy_designs(designs, author) }

      specify { expect { result }.to change { Event.count }.by(5) }

      # An addditional query due to event tracking
      specify { expect { result }.not_to exceed_query_limit(2) }

      it 'creates 5 destroyed design events' do
        ids = result.pluck('id')
        events = Event.destroyed_action.where(id: ids)

        expect(events.map(&:design)).to match_array(designs)
      end

      it_behaves_like "it records the event in the event counter" do
        let(:event_action) { :design_action }
      end

      it_behaves_like "it records a git write event"

      it_behaves_like 'Snowplow event tracking with RedisHLL context' do
        subject(:design_service) { service.destroy_designs([design], author) }

        let(:project) { design.project }
        let(:namespace) { project.namespace }
        let(:category) { described_class.name }
        let(:action) { 'destroy' }
        let(:user) { author }
        let(:property) { :design_action.to_s }
        let(:label) { ::EventCreateService::DEGIGN_EVENT_LABEL }
      end
    end
  end

  describe '#leave_note', :snowplow do
    subject(:leave_note) { service.leave_note(note, author) }

    let(:note) { create(:note) }
    let(:author) { create(:user) }
    let(:event_action) { :merge_request_action }

    it { expect(leave_note).to be_truthy }

    it "creates new event" do
      expect { leave_note }.to change { Event.count }.by(1)
    end

    context 'when it is a diff note' do
      let(:note) { create(:diff_note_on_merge_request) }

      it_behaves_like "it records the event in the event counter"

      it_behaves_like 'Snowplow event tracking with RedisHLL context' do
        let(:note) { create(:diff_note_on_merge_request) }
        let(:category) { described_class.name }
        let(:action) { 'commented' }
        let(:property) { described_class::MR_EVENT_PROPERTY }
        let(:label) { described_class::MR_EVENT_LABEL }
        let(:namespace) { project.namespace }
        let(:project) { note.project }
        let(:user) { author }
        let(:context) do
          [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: property).to_context]
        end
      end
    end

    context 'when it is not a diff note' do
      it 'does not change the unique action counter' do
        tracking_params = { event_names: event_action, start_date: Date.yesterday, end_date: Date.today }

        expect { subject }.not_to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) }
      end
    end
  end
end