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

module FeatureFlags
  class UpdateService < FeatureFlags::BaseService
    AUDITABLE_STRATEGY_ATTRIBUTES_HUMAN_NAMES = {
      'scopes' => 'environment scopes',
      'parameters' => 'parameters'
    }.freeze

    def success(**args)
      execute_hooks_after_commit(args[:feature_flag])
      super
    end

    def execute(feature_flag)
      return error('Access Denied', 403) unless can_update?(feature_flag)
      return error('Not Found', 404) unless valid_user_list_ids?(feature_flag, user_list_ids(params))

      ApplicationRecord.transaction do
        feature_flag.assign_attributes(params)

        feature_flag.strategies.each do |strategy|
          if strategy.name_changed? && strategy.name_was == ::Operations::FeatureFlags::Strategy::STRATEGY_GITLABUSERLIST
            strategy.user_list = nil
          end
        end

        # We generate the audit context before the feature flag is saved as #changed_strategies_messages depends on the strategies' states before save
        saved_audit_context = audit_context feature_flag

        if feature_flag.save
          update_last_feature_flag_updated_at!

          success(feature_flag: feature_flag, audit_context: saved_audit_context)
        else
          error(feature_flag.errors.full_messages, :bad_request)
        end
      end
    end

    private

    def execute_hooks_after_commit(feature_flag)
      return unless feature_flag.active_previously_changed?

      # The `current_user` method (defined in `BaseService`) is not available within the `run_after_commit` block
      user = current_user
      feature_flag.run_after_commit do
        HookService.new(feature_flag, user).execute
      end
    end

    def audit_context(feature_flag)
      {
        name: 'feature_flag_updated',
        message: audit_message(feature_flag),
        author: current_user,
        scope: feature_flag.project,
        target: feature_flag
      }
    end

    def audit_message(feature_flag)
      changes = changed_attributes_messages(feature_flag)
      changes += changed_strategies_messages(feature_flag)

      return if changes.empty?

      "Updated feature flag #{feature_flag.name}. " + changes.join(" ")
    end

    def changed_attributes_messages(feature_flag)
      feature_flag.changes.slice(*AUDITABLE_ATTRIBUTES).map do |attribute_name, changes|
        "Updated #{attribute_name} "\
        "from \"#{changes.first}\" to "\
        "\"#{changes.second}\"."
      end
    end

    def changed_strategies_messages(feature_flag)
      feature_flag.strategies.map do |strategy|
        if strategy.new_record?
          created_strategy_message(strategy)
        elsif strategy.marked_for_destruction?
          deleted_strategy_message(strategy)
        else
          updated_strategy_message(strategy)
        end
      end.compact # updated_strategy_message can return nil if nothing has been changed
    end

    def deleted_strategy_message(strategy)
      scopes = strategy.scopes.map { |scope| scope.environment_scope }.join(', ')
      "Deleted strategy #{strategy.name} with environment scopes #{scopes}."
    end

    def updated_strategy_message(strategy)
      changes = strategy.changes.slice(*AUDITABLE_STRATEGY_ATTRIBUTES_HUMAN_NAMES.keys)
      return if changes.empty?

      message = "Updated strategy #{strategy.name} "
      message += changes.map do |attribute_name, change|
        name = AUDITABLE_STRATEGY_ATTRIBUTES_HUMAN_NAMES[attribute_name]
        "#{name} from #{change.first} to #{change.second}"
      end.join(' ')

      message + '.'
    end

    def can_update?(feature_flag)
      Ability.allowed?(current_user, :update_feature_flag, feature_flag)
    end

    def user_list_ids(params)
      params.fetch(:strategies_attributes, [])
        .select { |s| s[:user_list_id].present? }
        .map { |s| s[:user_list_id] }
    end

    def valid_user_list_ids?(feature_flag, user_list_ids)
      user_list_ids.empty? || ::Operations::FeatureFlags::UserList.belongs_to?(feature_flag.project_id, user_list_ids)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe FeatureFlags::UpdateService, :with_license, feature_category: :feature_flags do
  let_it_be(:project) { create(:project) }
  let_it_be(:developer) { create(:user) }
  let_it_be(:reporter) { create(:user) }

  let(:user) { developer }
  let(:feature_flag) { create(:operations_feature_flag, project: project, active: true) }

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

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

    let(:params) { { name: 'new_name' } }
    let(:audit_event_details) { AuditEvent.last.details }
    let(:audit_event_message) { audit_event_details[:custom_message] }

    it 'returns success status' do
      expect(subject[:status]).to eq(:success)
    end

    context 'when Jira Connect subscription does not exist' do
      it 'does not sync the feature flag to Jira' do
        expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async)

        subject
      end
    end

    context 'when Jira Connect subscription exists' do
      before do
        create(:jira_connect_subscription, namespace: project.namespace)
      end

      it 'syncs the feature flag to Jira' do
        expect(::JiraConnect::SyncFeatureFlagsWorker).to receive(:perform_async).with(Integer, Integer)

        subject
      end
    end

    it 'creates audit event with correct message' do
      name_was = feature_flag.name

      expect { subject }.to change { AuditEvent.count }.by(1)
      expect(audit_event_message).to(
        eq("Updated feature flag new_name. "\
           "Updated name from \"#{name_was}\" "\
           "to \"new_name\".")
      )
    end

    it_behaves_like 'update feature flag client'

    context 'with invalid params' do
      let(:params) { { name: nil } }

      it 'returns error status' do
        expect(subject[:status]).to eq(:error)
        expect(subject[:http_status]).to eq(:bad_request)
      end

      it 'returns error messages' do
        expect(subject[:message]).to include("Name can't be blank")
      end

      it 'does not create audit event' do
        expect { subject }.not_to change { AuditEvent.count }
      end

      it 'does not sync the feature flag to Jira' do
        expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async)

        subject
      end

      it_behaves_like 'does not update feature flag client'
    end

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

      it 'returns error status' do
        expect(subject[:status]).to eq(:error)
        expect(subject[:message]).to eq('Access Denied')
      end
    end

    context 'when nothing is changed' do
      let(:params) { {} }

      it 'returns success status' do
        expect(subject[:status]).to eq(:success)
      end

      it 'does not create audit event' do
        expect { subject }.not_to change { AuditEvent.count }
      end
    end

    context 'description is being changed' do
      let(:params) { { description: 'new description' } }

      it 'creates audit event with changed description' do
        expect { subject }.to change { AuditEvent.count }.by(1)
        expect(audit_event_message).to(
          include("Updated description from \"\""\
                  " to \"new description\".")
        )
      end
    end

    context 'when flag active state is changed' do
      let(:params) do
        {
          active: false
        }
      end

      it 'creates audit event about changing active state' do
        expect { subject }.to change { AuditEvent.count }.by(1)
        expect(audit_event_message).to(
          include('Updated active from "true" to "false".')
        )
      end

      it 'executes hooks' do
        hook = create(:project_hook, :all_events_enabled, project: project)
        expect(WebHookWorker).to receive(:perform_async).with(hook.id, an_instance_of(Hash), 'feature_flag_hooks', an_instance_of(Hash))

        subject
      end
    end
  end
end


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

module FeatureFlags
  class CreateService < FeatureFlags::BaseService
    def execute
      return error('Access Denied', 403) unless can_create?
      return error('Version is invalid', :bad_request) unless valid_version?

      ApplicationRecord.transaction do
        feature_flag = project.operations_feature_flags.new(params)

        if feature_flag.save
          update_last_feature_flag_updated_at!

          success(feature_flag: feature_flag)
        else
          error(feature_flag.errors.full_messages, 400)
        end
      end
    end

    private

    def audit_context(feature_flag)
      {
        name: 'feature_flag_created',
        message: audit_message(feature_flag),
        author: current_user,
        scope: feature_flag.project,
        target: feature_flag
      }
    end

    def audit_message(feature_flag)
      message_parts = ["Created feature flag #{feature_flag.name} with description \"#{feature_flag.description}\"."]

      message_parts += feature_flag.strategies.map do |strategy|
        created_strategy_message(strategy)
      end

      message_parts.join(" ")
    end

    def can_create?
      Ability.allowed?(current_user, :create_feature_flag, project)
    end

    def valid_version?
      !params.key?(:version) || Operations::FeatureFlag.versions.key?(params[:version])
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe FeatureFlags::CreateService, feature_category: :feature_flags do
  let_it_be(:project) { create(:project) }
  let_it_be(:developer) { create(:user) }
  let_it_be(:reporter) { create(:user) }

  let(:user) { developer }

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

  describe '#execute' do
    subject do
      described_class.new(project, user, params).execute
    end

    let(:feature_flag) { subject[:feature_flag] }

    context 'when feature flag can not be created' do
      let(:params) { {} }

      it 'returns status error' do
        expect(subject[:status]).to eq(:error)
      end

      it 'returns validation errors' do
        expect(subject[:message]).to include("Name can't be blank")
      end

      it 'does not create audit log' do
        expect { subject }.not_to change { AuditEvent.count }
      end

      it 'does not sync the feature flag to Jira' do
        expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async)

        subject
      end

      it_behaves_like 'does not update feature flag client'
    end

    context 'when feature flag is saved correctly' do
      let(:audit_event_details) { AuditEvent.last.details }
      let(:audit_event_message) { audit_event_details[:custom_message] }
      let(:params) do
        {
          name: 'feature_flag',
          description: 'description',
          version: 'new_version_flag',
          strategies_attributes: [{ name: 'default', scopes_attributes: [{ environment_scope: '*' }], parameters: {} },
                                  { name: 'default', parameters: {}, scopes_attributes: [{ environment_scope: 'production' }] }]
        }
      end

      it 'returns status success' do
        expect(subject[:status]).to eq(:success)
      end

      it 'creates feature flag' do
        expect { subject }.to change { Operations::FeatureFlag.count }.by(1)
      end

      it_behaves_like 'update feature flag client'

      context 'when Jira Connect subscription does not exist' do
        it 'does not sync the feature flag to Jira' do
          expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async)

          subject
        end
      end

      context 'when Jira Connect subscription exists' do
        before do
          create(:jira_connect_subscription, namespace: project.namespace)
        end

        it 'syncs the feature flag to Jira' do
          expect(::JiraConnect::SyncFeatureFlagsWorker).to receive(:perform_async).with(Integer, Integer)

          subject
        end
      end

      it 'creates audit event', :with_license do
        expect { subject }.to change { AuditEvent.count }.by(1)
        expect(audit_event_message).to start_with('Created feature flag feature_flag with description "description".')
        expect(audit_event_message).to include('Created strategy "default" with scopes "*".')
        expect(audit_event_message).to include('Created strategy "default" with scopes "production".')
      end

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

        it 'returns error status' do
          expect(subject[:status]).to eq(:error)
          expect(subject[:message]).to eq('Access Denied')
        end
      end
    end
  end
end


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

module IssueLinks
  class DestroyService < IssuableLinks::DestroyService
    include IncidentManagement::UsageData

    private

    def permission_to_remove_relation?
      can?(current_user, :admin_issue_link, source) && can?(current_user, :admin_issue_link, target)
    end

    def track_event
      track_incident_action(current_user, target, :incident_unrelate)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe IssueLinks::DestroyService, feature_category: :team_planning do
  describe '#execute' do
    let_it_be(:project) { create(:project_empty_repo, :private) }
    let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } }
    let_it_be(:issue_a) { create(:issue, project: project) }
    let_it_be(:issue_b) { create(:issue, project: project) }

    let!(:issuable_link) { create(:issue_link, source: issue_a, target: issue_b) }
    let(:user) { reporter }

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

    it_behaves_like 'a destroyable issuable link'

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

      it_behaves_like 'an incident management tracked event', :incident_management_incident_unrelate do
        let(:current_user) { reporter }
      end

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


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

module IssueLinks
  class ListService < IssuableLinks::ListService
    extend ::Gitlab::Utils::Override

    private

    def child_issuables
      issuable.related_issues(current_user, preload: preload_for_collection)
    end

    override :serializer
    def serializer
      LinkedProjectIssueSerializer
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe IssueLinks::ListService, feature_category: :team_planning do
  let(:user) { create :user }
  let(:project) { create(:project_empty_repo, :private) }
  let(:issue) { create :issue, project: project }
  let(:user_role) { :developer }

  before do
    project.add_role(user, user_role)
  end

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

    context 'user can see all issues' do
      let(:issue_b) { create :issue, project: project }
      let(:issue_c) { create :issue, project: project }
      let(:issue_d) { create :issue, project: project }

      let!(:issue_link_c) do
        create(:issue_link, source: issue_d, target: issue)
      end

      let!(:issue_link_b) do
        create(:issue_link, source: issue, target: issue_c)
      end

      let!(:issue_link_a) do
        create(:issue_link, source: issue, target: issue_b)
      end

      it 'ensures no N+1 queries are made' do
        control_count = ActiveRecord::QueryRecorder.new { subject }.count

        project = create :project, :public
        milestone = create :milestone, project: project
        issue_x = create :issue, project: project, milestone: milestone
        issue_y = create :issue, project: project, assignees: [user]
        issue_z = create :issue, project: project
        create :issue_link, source: issue_x, target: issue_y
        create :issue_link, source: issue_x, target: issue_z
        create :issue_link, source: issue_y, target: issue_z

        expect { subject }.not_to exceed_query_limit(control_count)
      end

      it 'returns related issues JSON' do
        expect(subject.size).to eq(3)

        expect(subject).to include(include(
          id: issue_b.id,
          title: issue_b.title,
          state: issue_b.state,
          reference: issue_b.to_reference(project),
          path: "/#{project.full_path}/-/issues/#{issue_b.iid}",
          relation_path: "/#{project.full_path}/-/issues/#{issue.iid}/links/#{issue_link_a.id}"
        ))

        expect(subject).to include(include(
          id: issue_c.id,
          title: issue_c.title,
          state: issue_c.state,
          reference: issue_c.to_reference(project),
          path: "/#{project.full_path}/-/issues/#{issue_c.iid}",
          relation_path: "/#{project.full_path}/-/issues/#{issue.iid}/links/#{issue_link_b.id}"
        ))

        expect(subject).to include(include(
          id: issue_d.id,
          title: issue_d.title,
          state: issue_d.state,
          reference: issue_d.to_reference(project),
          path: "/#{project.full_path}/-/issues/#{issue_d.iid}",
          relation_path: "/#{project.full_path}/-/issues/#{issue.iid}/links/#{issue_link_c.id}"
        ))
      end
    end

    context 'referencing a public project issue' do
      let(:public_project) { create :project, :public }
      let(:issue_b) { create :issue, project: public_project }

      let!(:issue_link) do
        create(:issue_link, source: issue, target: issue_b)
      end

      it 'presents issue' do
        expect(subject.size).to eq(1)
      end
    end

    context 'referencing issue with removed relationships' do
      context 'when referenced a deleted issue' do
        let(:issue_b) { create :issue, project: project }
        let!(:issue_link) do
          create(:issue_link, source: issue, target: issue_b)
        end

        it 'ignores issue' do
          issue_b.destroy!

          is_expected.to eq([])
        end
      end

      context 'when referenced an issue with deleted project' do
        let(:issue_b) { create :issue, project: project }
        let!(:issue_link) do
          create(:issue_link, source: issue, target: issue_b)
        end

        it 'ignores issue' do
          project.destroy!

          is_expected.to eq([])
        end
      end

      context 'when referenced an issue with deleted namespace' do
        let(:issue_b) { create :issue, project: project }
        let!(:issue_link) do
          create(:issue_link, source: issue, target: issue_b)
        end

        it 'ignores issue' do
          project.namespace.destroy!

          is_expected.to eq([])
        end
      end
    end

    context 'user cannot see relations' do
      context 'when user cannot see the referenced issue' do
        let!(:issue_link) do
          create(:issue_link, source: issue)
        end

        it 'returns an empty list' do
          is_expected.to eq([])
        end
      end

      context 'when user cannot see the issue that referenced' do
        let!(:issue_link) do
          create(:issue_link, target: issue)
        end

        it 'returns an empty list' do
          is_expected.to eq([])
        end
      end
    end

    context 'remove relations' do
      let!(:issue_link) do
        create(:issue_link, source: issue, target: referenced_issue)
      end

      context 'user can admin related issues just on target project' do
        let(:user_role) { :guest }
        let(:target_project) { create :project }
        let(:referenced_issue) { create :issue, project: target_project }

        it 'returns no destroy relation path' do
          target_project.add_developer(user)

          expect(subject.first[:relation_path]).to be_nil
        end
      end

      context 'user can admin related issues just on source project' do
        let(:user_role) { :developer }
        let(:target_project) { create :project }
        let(:referenced_issue) { create :issue, project: target_project }

        it 'returns no destroy relation path' do
          target_project.add_guest(user)

          expect(subject.first[:relation_path]).to be_nil
        end
      end

      context 'when user can admin related issues on both projects' do
        let(:referenced_issue) { create :issue, project: project }

        it 'returns related issue destroy relation path' do
          expect(subject.first[:relation_path])
            .to eq("/#{project.full_path}/-/issues/#{issue.iid}/links/#{issue_link.id}")
        end
      end
    end
  end
end


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

module IssueLinks
  class CreateService < IssuableLinks::CreateService
    include IncidentManagement::UsageData

    def linkable_issuables(issues)
      @linkable_issuables ||= issues.select { |issue| can?(current_user, :admin_issue_link, issue) }
    end

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

    private

    def readonly_issuables(issuables)
      @readonly_issuables ||= issuables.select { |issuable| issuable.readable_by?(current_user) }
    end

    def track_event
      track_incident_action(current_user, issuable, :incident_relate)
    end

    def link_class
      IssueLink
    end
  end
end

IssueLinks::CreateService.prepend_mod_with('IssueLinks::CreateService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe IssueLinks::CreateService, feature_category: :team_planning do
  describe '#execute' do
    let_it_be(:user) { create :user }
    let_it_be(:namespace) { create :namespace }
    let_it_be(:project) { create :project, namespace: namespace }
    let_it_be(:issuable) { create :issue, project: project }
    let_it_be(:issuable2) { create :issue, project: project }
    let_it_be(:restricted_issuable) { create :issue }
    let_it_be(:another_project) { create :project, namespace: project.namespace }
    let_it_be(:issuable3) { create :issue, project: another_project }
    let_it_be(:issuable_a) { create :issue, project: project }
    let_it_be(:issuable_b) { create :issue, project: project }
    let_it_be(:issuable_link) { create :issue_link, source: issuable, target: issuable_b, link_type: IssueLink::TYPE_RELATES_TO }

    let(:issuable_parent) { issuable.project }
    let(:issuable_type) { :issue }
    let(:issuable_link_class) { IssueLink }
    let(:params) { {} }

    before do
      project.add_developer(user)
      restricted_issuable.project.add_guest(user)
      another_project.add_developer(user)
    end

    it_behaves_like 'issuable link creation'

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

      let(:params) do
        { issuable_references: [issuable2.to_reference, issuable3.to_reference(another_project)] }
      end

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

      it_behaves_like 'an incident management tracked event', :incident_management_incident_relate do
        let(:current_user) { user }
      end

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


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

module Spam
  class SpamActionService
    include SpamConstants

    def initialize(spammable:, user:, action:, extra_features: {})
      @target = spammable
      @user = user
      @action = action
      @extra_features = extra_features
    end

    def execute
      return ServiceResponse.success(message: 'Skipped spam check because user was not present') unless user

      if target.supports_recaptcha? && spam_params
        execute_with_captcha_support
      else
        execute_spam_check
      end
    end

    delegate :check_for_spam?, to: :target

    private

    attr_reader :user, :action, :target, :spam_log, :extra_features

    def spam_params
      Gitlab::RequestContext.instance.spam_params
    end

    def execute_with_captcha_support
      recaptcha_verified = Captcha::CaptchaVerificationService.new(spam_params: spam_params).execute

      if recaptcha_verified
        # If it's a request which is already verified through CAPTCHA,
        # update the spam log accordingly.
        SpamLog.verify_recaptcha!(user_id: user.id, id: spam_params.spam_log_id)
        ServiceResponse.success(message: "CAPTCHA successfully verified")
      else
        execute_spam_check
      end
    end

    def execute_spam_check
      return ServiceResponse.success(message: 'Skipped spam check because user was allowlisted') if allowlisted?(user)
      return ServiceResponse.success(message: 'Skipped spam check because it was not required') unless check_for_spam?(user: user)

      perform_spam_service_check
      ServiceResponse.success(message: "Spam check performed. Check #{target.class.name} spammable model for any errors or CAPTCHA requirement")
    end

    ##
    # In order to be proceed to the spam check process, the target must be
    # a dirty instance, which means it should be already assigned with the new
    # attribute values.
    def ensure_target_is_dirty
      msg = "Target instance of #{target.class.name} must be dirty (must have changes to save)"
      raise(msg) unless target.has_changes_to_save?
    end

    def allowlisted?(user)
      user.try(:gitlab_bot?) || user.try(:gitlab_service_user?)
    end

    ##
    # Performs the spam check using the spam verdict service, and modifies the target model
    # accordingly based on the result.
    def perform_spam_service_check
      ensure_target_is_dirty

      # since we can check for spam, and recaptcha is not verified,
      # ask the SpamVerdictService what to do with the target.
      spam_verdict_service.execute.tap do |result|
        case result
        when BLOCK_USER
          target.spam!
          create_spam_log
          create_spam_abuse_event(result)
          ban_user!
        when DISALLOW
          target.spam!
          create_spam_log
          create_spam_abuse_event(result)
        when CONDITIONAL_ALLOW
          # This means "require a CAPTCHA to be solved"
          target.needs_recaptcha!
          create_spam_log
          create_spam_abuse_event(result)
        when OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM
          create_spam_log
        when ALLOW
          target.clear_spam_flags!
        when NOOP
          # spamcheck is not explicitly rendering a verdict & therefore can't make a decision
          target.clear_spam_flags!
        end
      end
    end

    def create_spam_log
      @spam_log = SpamLog.create!(
        {
          user_id: user.id,
          title: target.spam_title,
          description: target.spam_description,
          source_ip: spam_params&.ip_address,
          user_agent: spam_params&.user_agent,
          noteable_type: noteable_type,
          # Now, all requests are via the API, so hardcode it to true to simplify the logic and API
          # of this service.  See https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/2266
          # for original introduction of `via_api` field.
          # See discussion here about possibly deprecating this field:
          # https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/2266#note_542527450
          via_api: true
        }
      )

      target.spam_log = spam_log
    end

    def create_spam_abuse_event(result)
      params = {
        user_id: user.id,
        title: target.spam_title,
        description: target.spam_description,
        source_ip: spam_params&.ip_address,
        user_agent: spam_params&.user_agent,
        noteable_type: noteable_type,
        verdict: result
      }

      target.run_after_commit_or_now do
        Abuse::SpamAbuseEventsWorker.perform_async(params)
      end
    end

    def ban_user!
      UserCustomAttribute.set_banned_by_spam_log(target.spam_log)

      user.ban!
    end

    def spam_verdict_service
      context = {
        action: action,
        target_type: noteable_type
      }

      options = {
        ip_address: spam_params&.ip_address,
        user_agent: spam_params&.user_agent,
        referer: spam_params&.referer
      }

      SpamVerdictService.new(
        target: target,
        user: user,
        options: options,
        context: context,
        extra_features: extra_features
      )
    end

    def noteable_type
      @notable_type ||= target.class.to_s
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Spam::SpamActionService, feature_category: :instance_resiliency do
  include_context 'includes Spam constants'

  let(:issue) { create(:issue, project: project, author: author) }
  let(:personal_snippet) { create(:personal_snippet, :public, author: author) }
  let(:project_snippet) { create(:project_snippet, :public, author: author) }
  let(:fake_ip) { '1.2.3.4' }
  let(:fake_user_agent) { 'fake-user-agent' }
  let(:fake_referer) { 'fake-http-referer' }
  let(:captcha_response) { 'abc123' }
  let(:spam_log_id) { existing_spam_log.id }
  let(:spam_params) do
    ::Spam::SpamParams.new(
      captcha_response: captcha_response,
      spam_log_id: spam_log_id,
      ip_address: fake_ip,
      user_agent: fake_user_agent,
      referer: fake_referer
    )
  end

  let_it_be(:project) { create(:project, :public) }
  let_it_be(:user) { create(:user) }
  let_it_be(:author) { create(:user) }

  before do
    issue.spam = false
    personal_snippet.spam = false

    allow_next_instance_of(described_class) do |service|
      allow(service).to receive(:spam_params).and_return(spam_params)
    end
  end

  describe 'constructor argument validation' do
    subject do
      described_service = described_class.new(spammable: issue, user: user, action: :create)
      described_service.execute
    end

    context 'when user is nil' do
      let(:spam_params) { true }
      let(:user) { nil }
      let(:expected_service_user_not_present_message) do
        /Skipped spam check because user was not present/
      end

      it "returns success with a messaage" do
        response = subject

        expect(response.message).to match(expected_service_user_not_present_message)
        expect(issue).not_to be_spam
      end
    end
  end

  shared_examples 'allows user' do
    it 'does not perform spam check' do
      expect(Spam::SpamVerdictService).not_to receive(:new)

      response = subject

      expect(response.message).to match(/user was allowlisted/)
    end
  end

  shared_examples 'creates a spam log' do |target_type|
    it do
      expect { subject }
        .to log_spam(title: target.title, description: target.description, noteable_type: target_type)

      # TODO: These checks should be incorporated into the `log_spam` RSpec matcher above
      new_spam_log = SpamLog.last
      expect(new_spam_log.user_id).to eq(user.id)
      expect(new_spam_log.title).to eq(target.title)
      expect(new_spam_log.description).to eq(target.spam_description)
      expect(new_spam_log.source_ip).to eq(fake_ip)
      expect(new_spam_log.user_agent).to eq(fake_user_agent)
      expect(new_spam_log.noteable_type).to eq(target_type)
      expect(new_spam_log.via_api).to eq(true)
    end
  end

  shared_examples 'calls SpamAbuseEventsWorker with correct arguments' do
    let(:params) do
      {
        user_id: user.id,
        title: target.title,
        description: target.spam_description,
        source_ip: fake_ip,
        user_agent: fake_user_agent,
        noteable_type: target_type,
        verdict: verdict
      }
    end

    it do
      expect(::Abuse::SpamAbuseEventsWorker).to receive(:perform_async).with(params)

      subject
    end
  end

  shared_examples 'execute spam action service' do |target_type|
    let(:fake_captcha_verification_service) { double(:captcha_verification_service) }
    let(:fake_verdict_service) { double(:spam_verdict_service) }

    let(:verdict_service_opts) do
      {
        ip_address: fake_ip,
        user_agent: fake_user_agent,
        referer: fake_referer
      }
    end

    let(:verdict_service_args) do
      {
        target: target,
        user: user,
        options: verdict_service_opts,
        context: {
          action: :create,
          target_type: target_type
        },
        extra_features: extra_features
      }
    end

    let_it_be(:existing_spam_log) { create(:spam_log, user: user, recaptcha_verified: false) }

    subject do
      described_service = described_class.new(spammable: target, extra_features:
                                              extra_features, user: user, action: :create)
      described_service.execute
    end

    before do
      allow(Captcha::CaptchaVerificationService).to receive(:new).with(spam_params: spam_params) { fake_captcha_verification_service }
      allow(Spam::SpamVerdictService).to receive(:new).with(verdict_service_args).and_return(fake_verdict_service)
      allow(fake_verdict_service).to receive(:execute).and_return({})
    end

    context 'when captcha response verification returns true' do
      before do
        allow(fake_captcha_verification_service)
          .to receive(:execute).and_return(true)
      end

      it "doesn't check with the SpamVerdictService" do
        aggregate_failures do
          expect(SpamLog).to receive(:verify_recaptcha!).with(
            user_id: user.id,
            id: spam_log_id
          )
          expect(fake_verdict_service).not_to receive(:execute)
        end

        subject
      end

      it 'updates spam log' do
        expect { subject }.to change { existing_spam_log.reload.recaptcha_verified }.from(false).to(true)
      end
    end

    context 'when captcha response verification returns false' do
      before do
        allow(fake_captcha_verification_service)
          .to receive(:execute).and_return(false)
      end

      context 'when spammable attributes have not changed' do
        before do
          allow(target).to receive(:has_changes_to_save?).and_return(true)
        end

        it 'does not create a spam log' do
          expect { subject }.not_to change(SpamLog, :count)
        end

        it 'does not call SpamAbuseEventsWorker' do
          expect(::Abuse::SpamAbuseEventsWorker).not_to receive(:perform_async)

          subject
        end
      end

      context 'when spammable attributes have changed' do
        let(:expected_service_check_response_message) do
          /Check #{target_type} spammable model for any errors or CAPTCHA requirement/
        end

        before do
          target.description = 'Lovely Spam! Wonderful Spam!'
        end

        context 'when captcha is not supported' do
          before do
            allow(target).to receive(:supports_recaptcha?).and_return(false)
          end

          it "does not execute with captcha support" do
            expect(Captcha::CaptchaVerificationService).not_to receive(:new)

            subject
          end

          it "executes a spam check" do
            expect(fake_verdict_service).to receive(:execute)

            subject
          end
        end

        context 'when user is a gitlab bot' do
          before do
            allow(user).to receive(:gitlab_bot?).and_return(true)
          end

          it_behaves_like 'allows user'
        end

        context 'when user is a gitlab service user' do
          before do
            allow(user).to receive(:gitlab_service_user?).and_return(true)
          end

          it_behaves_like 'allows user'
        end

        context 'when disallowed by the spam verdict service' do
          before do
            allow(fake_verdict_service).to receive(:execute).and_return(DISALLOW)
          end

          it_behaves_like 'creates a spam log', target_type

          it_behaves_like 'calls SpamAbuseEventsWorker with correct arguments' do
            let(:verdict) { DISALLOW }
            let(:target_type) { target_type }
          end

          it 'marks as spam' do
            response = subject

            expect(response.message).to match(expected_service_check_response_message)
            expect(target).to be_spam
          end
        end

        context 'spam verdict service advises to block the user' do
          # create a fresh user to ensure it is in the unbanned state
          let(:user) { create(:user) }

          before do
            allow(fake_verdict_service).to receive(:execute).and_return(BLOCK_USER)
          end

          it_behaves_like 'creates a spam log', target_type

          it_behaves_like 'calls SpamAbuseEventsWorker with correct arguments' do
            let(:verdict) { BLOCK_USER }
            let(:target_type) { target_type }
          end

          it 'marks as spam' do
            response = subject

            expect(response.message).to match(expected_service_check_response_message)
            expect(target).to be_spam
          end

          it 'bans the user' do
            subject

            custom_attribute = user.custom_attributes.by_key(UserCustomAttribute::AUTO_BANNED_BY_SPAM_LOG_ID).first
            expect(custom_attribute.value).to eq(target.spam_log.id.to_s)
            expect(user).to be_banned
          end
        end

        context 'when spam verdict service conditionally allows' do
          before do
            allow(fake_verdict_service).to receive(:execute).and_return(CONDITIONAL_ALLOW)
          end

          it_behaves_like 'creates a spam log', target_type

          it_behaves_like 'calls SpamAbuseEventsWorker with correct arguments' do
            let(:verdict) { CONDITIONAL_ALLOW }
            let(:target_type) { target_type }
          end

          it 'does not mark as spam' do
            response = subject

            expect(response.message).to match(expected_service_check_response_message)
            expect(target).not_to be_spam
          end

          it 'marks as needing reCAPTCHA' do
            response = subject

            expect(response.message).to match(expected_service_check_response_message)
            expect(target).to be_needs_recaptcha
          end
        end

        context 'when spam verdict service returns OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM' do
          before do
            allow(fake_verdict_service).to receive(:execute).and_return(OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM)
          end

          it_behaves_like 'creates a spam log', target_type

          it 'does not call SpamAbuseEventsWorker' do
            expect(::Abuse::SpamAbuseEventsWorker).not_to receive(:perform_async)

            subject
          end

          it 'does not mark as spam' do
            response = subject

            expect(response.message).to match(expected_service_check_response_message)
            expect(target).not_to be_spam
          end

          it 'does not mark as needing CAPTCHA' do
            response = subject

            expect(response.message).to match(expected_service_check_response_message)
            expect(target).not_to be_needs_recaptcha
          end
        end

        context 'when spam verdict service allows creation' do
          before do
            allow(fake_verdict_service).to receive(:execute).and_return(ALLOW)
          end

          it 'does not create a spam log' do
            expect { subject }.not_to change(SpamLog, :count)
          end

          it 'does not call SpamAbuseEventsWorker' do
            expect(::Abuse::SpamAbuseEventsWorker).not_to receive(:perform_async)

            subject
          end

          it 'clears spam flags' do
            expect(target).to receive(:clear_spam_flags!)

            subject
          end
        end

        context 'when spam verdict service returns noop' do
          before do
            allow(fake_verdict_service).to receive(:execute).and_return(NOOP)
          end

          it 'does not create a spam log' do
            expect { subject }.not_to change(SpamLog, :count)
          end

          it 'does not call SpamAbuseEventsWorker' do
            expect(::Abuse::SpamAbuseEventsWorker).not_to receive(:perform_async)

            subject
          end

          it 'clears spam flags' do
            expect(target).to receive(:clear_spam_flags!)

            subject
          end
        end

        context 'with spam verdict service options' do
          before do
            allow(fake_verdict_service).to receive(:execute).and_return(ALLOW)
          end

          it 'assembles the options with information from the request' do
            expect(Spam::SpamVerdictService).to receive(:new).with(verdict_service_args)

            subject
          end
        end
      end
    end
  end

  describe '#execute' do
    describe 'issue' do
      let(:target) { issue }
      let(:extra_features) { {} }

      it_behaves_like 'execute spam action service', 'Issue'
    end

    describe 'project snippet' do
      let(:target) { project_snippet }
      let(:extra_features) { { files: [{ path: 'project.rb' }] } }

      it_behaves_like 'execute spam action service', 'ProjectSnippet'
    end

    describe 'personal snippet' do
      let(:target) { personal_snippet }
      let(:extra_features) { { files: [{ path: 'personal.rb' }] } }

      it_behaves_like 'execute spam action service', 'PersonalSnippet'
    end
  end
end


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

module Spam
  class HamService
    include AkismetMethods

    attr_accessor :spam_log, :options

    def initialize(spam_log)
      @spam_log = spam_log
      @user = spam_log.user
      @options = {
          ip_address: spam_log.source_ip,
          user_agent: spam_log.user_agent
      }
    end

    def execute
      if akismet.submit_ham
        spam_log.update_attribute(:submitted_as_ham, true)
      else
        false
      end
    end

    alias_method :target, :spam_log
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Spam::HamService, feature_category: :instance_resiliency do
  let_it_be(:user) { create(:user) }

  let!(:spam_log) { create(:spam_log, user: user, submitted_as_ham: false) }
  let(:fake_akismet_service) { double(:akismet_service) }

  subject { described_class.new(spam_log) }

  before do
    allow(Spam::AkismetService).to receive(:new).and_return fake_akismet_service
  end

  describe '#execute' do
    context 'AkismetService returns false (Akismet cannot be reached, etc)' do
      before do
        allow(fake_akismet_service).to receive(:submit_ham).and_return false
      end

      it 'returns false' do
        expect(subject.execute).to be_falsey
      end

      it 'does not update the record' do
        expect { subject.execute }.not_to change { spam_log.submitted_as_ham }
      end

      context 'if spam log record has already been marked as spam' do
        before do
          spam_log.update_attribute(:submitted_as_ham, true)
        end

        it 'does not update the record' do
          expect { subject.execute }.not_to change { spam_log.submitted_as_ham }
        end
      end
    end

    context 'Akismet ham submission is successful' do
      before do
        spam_log.update_attribute(:submitted_as_ham, false)
        allow(fake_akismet_service).to receive(:submit_ham).and_return true
      end

      it 'returns true' do
        expect(subject.execute).to be_truthy
      end

      it 'updates the record' do
        expect { subject.execute }.to change { spam_log.submitted_as_ham }.from(false).to(true)
      end
    end
  end
end


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

module Spam
  class AkismetService
    attr_accessor :text, :options

    def initialize(owner_name, owner_email, text, options = {})
      @owner_name = owner_name
      @owner_email = owner_email
      @text = text
      @options = options
    end

    def spam?
      return false unless akismet_enabled?

      params = {
        type: 'comment',
        text: text,
        created_at: DateTime.current,
        author: owner_name,
        author_email: owner_email,
        # NOTE: The akismet_client needs the option to be named `:referrer`, not `:referer`
        referrer: options[:referer]
      }

      begin
        is_spam, is_blatant = akismet_client.check(options[:ip_address], options[:user_agent], params)
        is_spam || is_blatant
      rescue ArgumentError => e
        Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
        false
      rescue StandardError => e
        Gitlab::ErrorTracking.track_exception(e)
        Gitlab::AppLogger.error("Error during Akismet spam check, flagging as not spam: #{e}")
        false
      end
    end

    def submit_ham
      submit(:ham)
    end

    def submit_spam
      submit(:spam)
    end

    private

    attr_accessor :owner_name, :owner_email

    def akismet_client
      @akismet_client ||= ::Akismet::Client.new(
        Gitlab::CurrentSettings.akismet_api_key,
        Gitlab.config.gitlab.url
      )
    end

    def akismet_enabled?
      Gitlab::CurrentSettings.akismet_enabled
    end

    def submit(type)
      return false unless akismet_enabled?

      params = {
        type: 'comment',
        text: text,
        author: owner_name,
        author_email: owner_email
      }

      begin
        akismet_client.public_send(type, options[:ip_address], options[:user_agent], params) # rubocop:disable GitlabSecurity/PublicSend
        true
      rescue StandardError => e
        Gitlab::AppLogger.error("Unable to connect to Akismet: #{e}, skipping!")
        false
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Spam::AkismetService, feature_category: :instance_resiliency do
  let(:fake_akismet_client) { double(:akismet_client) }
  let(:ip) { '1.2.3.4' }
  let(:user_agent) { 'some user_agent' }
  let(:referer) { 'some referer' }

  let_it_be(:text) { "Would you like to buy some tinned meat product?" }
  let_it_be(:spam_owner) { create(:user) }

  subject do
    options = { ip_address: ip, user_agent: user_agent, referer: referer }
    described_class.new(spam_owner.name, spam_owner.email, text, options)
  end

  before do
    stub_application_setting(akismet_enabled: true)
    allow(subject).to receive(:akismet_client).and_return(fake_akismet_client)
  end

  shared_examples 'no activity if Akismet is not enabled' do |method_call|
    before do
      stub_application_setting(akismet_enabled: false)
    end

    it 'is automatically false' do
      expect(subject.send(method_call)).to be_falsey
    end

    it 'performs no check' do
      expect(fake_akismet_client).not_to receive(:public_send)

      subject.send(method_call)
    end
  end

  shared_examples 'false if Akismet is not available' do |method_call|
    context 'if Akismet is not available' do
      before do
        allow(fake_akismet_client).to receive(:public_send).and_raise(StandardError.new("oh noes!"))
      end

      specify do
        expect(subject.send(method_call)).to be_falsey
      end

      it 'logs an error' do
        expect(Gitlab::AppLogger).to receive(:error).with(/skipping/)

        subject.send(method_call)
      end
    end
  end

  describe '#spam?' do
    it_behaves_like 'no activity if Akismet is not enabled', :spam?, :check

    context 'if Akismet is enabled' do
      it 'correctly transforms options for the akismet client, including spelling of referrer key' do
        expected_check_params = {
          type: 'comment',
          text: text,
          created_at: anything,
          author: spam_owner.name,
          author_email: spam_owner.email,
          # NOTE: The akismet_client needs the option to be named `:referrer`, not `:referer`
          referrer: referer
        }

        expect(fake_akismet_client).to receive(:check).with(ip, user_agent, expected_check_params)
        subject.spam?
      end

      context 'the text is spam' do
        before do
          allow(fake_akismet_client).to receive(:check).and_return([true, false])
        end

        specify do
          expect(subject.spam?).to be_truthy
        end
      end

      context 'the text is blatant spam' do
        before do
          allow(fake_akismet_client).to receive(:check).and_return([false, true])
        end

        specify do
          expect(subject.spam?).to be_truthy
        end
      end

      context 'the text is not spam' do
        before do
          allow(fake_akismet_client).to receive(:check).and_return([false, false])
        end

        specify do
          expect(subject.spam?).to be_falsey
        end
      end

      describe 'error handling' do
        before do
          allow(fake_akismet_client).to receive(:check).and_raise(error)
        end

        context 'StandardError other than ArgumentError is raised' do
          let(:error) { Akismet::Error.new("Lovely spam! Wonderful spam!") }

          specify do
            expect(subject.spam?).to be_falsey
          end

          it 'logs an error' do
            expect(Gitlab::AppLogger).to receive(:error).with(/Error during Akismet.*flagging as not spam.*Lovely spam/)

            subject.spam?
          end
        end

        context 'ArgumentError is raised in dev' do
          let(:error) { ArgumentError }

          it 'raises original error' do
            expect { subject.spam? }.to raise_error(error)
          end
        end
      end
    end
  end

  describe '#submit_ham' do
    it_behaves_like 'no activity if Akismet is not enabled', :submit_ham
    it_behaves_like 'false if Akismet is not available', :submit_ham

    context 'if Akismet is available' do
      specify do
        expect(fake_akismet_client).to receive(:public_send).with(:ham, any_args)

        expect(subject.submit_ham).to be_truthy
      end
    end
  end

  describe '#submit_spam' do
    it_behaves_like 'no activity if Akismet is not enabled', :submit_spam
    it_behaves_like 'false if Akismet is not available', :submit_spam

    context 'if Akismet is available' do
      specify do
        expect(fake_akismet_client).to receive(:public_send).with(:spam, any_args)

        expect(subject.submit_spam).to be_truthy
      end
    end
  end
end


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

module Spam
  class SpamVerdictService
    include AkismetMethods
    include SpamConstants

    def initialize(user:, target:, options:, context: {}, extra_features: {})
      @target = target
      @user = user
      @options = options
      @context = context
      @extra_features = extra_features
    end

    def execute
      spamcheck_verdict = nil

      external_spam_check_round_trip_time = Benchmark.realtime do
        spamcheck_verdict = get_spamcheck_verdict
      end

      histogram.observe({ result: spamcheck_verdict.upcase }, external_spam_check_round_trip_time) if spamcheck_verdict

      akismet_verdict = get_akismet_verdict

      # filter out anything we don't recognise, including nils.
      valid_verdicts = [spamcheck_verdict, akismet_verdict].compact.select { |r| SUPPORTED_VERDICTS.key?(r) }

      # Treat nils - such as service unavailable - as ALLOW
      return ALLOW unless valid_verdicts.any?

      # Favour the most restrictive verdict
      final_verdict = valid_verdicts.min_by { |v| SUPPORTED_VERDICTS[v][:priority] }

      # The target can override the verdict via the `allow_possible_spam` application setting
      final_verdict = OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM if override_via_allow_possible_spam?(verdict: final_verdict)

      logger.info(
        class: self.class.name,
        akismet_verdict: akismet_verdict,
        spam_check_verdict: spamcheck_verdict,
        spam_check_rtt: external_spam_check_round_trip_time.real,
        final_verdict: final_verdict,
        username: user.username,
        user_id: user.id,
        target_type: target.class.to_s,
        project_id: target.project_id
      )

      final_verdict
    end

    private

    attr_reader :user, :target, :options, :context, :extra_features

    def get_akismet_verdict
      if akismet.spam?
        Gitlab::Recaptcha.enabled? ? CONDITIONAL_ALLOW : DISALLOW
      else
        ALLOW
      end
    end

    def get_spamcheck_verdict
      return unless Gitlab::CurrentSettings.spam_check_endpoint_enabled

      begin
        result = spamcheck_client.spam?(spammable: target, user: user, context: context, extra_features: extra_features)

        if result.evaluated?
          Abuse::TrustScore.create!(user: user, score: result.score, source: :spamcheck)
        end

        result.verdict

      rescue StandardError => e
        Gitlab::ErrorTracking.log_exception(e, error: ERROR_TYPE)
        nil
      end
    end

    def override_via_allow_possible_spam?(verdict:)
      # If the verdict is already going to allow (because current verdict's priority value is greater
      # than the override verdict's priority value), then we don't need to override it.
      return false if SUPPORTED_VERDICTS[verdict][:priority] > SUPPORTED_VERDICTS[OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM][:priority]

      allow_possible_spam?
    end

    def allow_possible_spam?
      target.allow_possible_spam?(user) || user.trusted?
    end

    def spamcheck_client
      @spamcheck_client ||= Gitlab::Spamcheck::Client.new
    end

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

    def histogram
      @histogram ||= Gitlab::Metrics.histogram(:gitlab_spamcheck_request_duration_seconds, 'Request duration to the anti-spam service')
    end
  end
end

Spam::SpamVerdictService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency do
  include_context 'includes Spam constants'

  let(:fake_ip) { '1.2.3.4' }
  let(:fake_user_agent) { 'fake-user-agent' }
  let(:fake_referer) { 'fake-http-referer' }
  let(:env) do
    { 'action_dispatch.remote_ip' => fake_ip,
      'HTTP_USER_AGENT' => fake_user_agent,
      'HTTP_REFERER' => fake_referer }
  end

  let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::ALLOW }
  let(:verdict_score) { 0.01 }
  let(:verdict_evaluated) { true }

  let(:response) do
    response = ::Spamcheck::SpamVerdict.new
    response.verdict = verdict_value
    response.score = verdict_score
    response.evaluated = verdict_evaluated
    response
  end

  let(:spam_client_result) do
    Gitlab::Spamcheck::Result.new(response)
  end

  let(:check_for_spam) { true }
  let_it_be_with_reload(:user) { create(:user) }
  let_it_be(:issue) { create(:issue, author: user) }
  let_it_be(:snippet) { create(:personal_snippet, :public, author: user) }

  let(:service) do
    described_class.new(user: user, target: target, options: {})
  end

  shared_examples 'execute spam verdict service' do
    subject(:execute) { service.execute }

    before do
      allow(service).to receive(:get_akismet_verdict).and_return(nil)
      allow(service).to receive(:get_spamcheck_verdict).and_return(nil)
    end

    context 'if all services return nil' do
      it 'renders ALLOW verdict' do
        is_expected.to eq ALLOW
      end
    end

    context 'if only one service returns a verdict' do
      context 'and it is supported' do
        before do
          allow(service).to receive(:get_akismet_verdict).and_return(DISALLOW)
        end

        it 'renders that verdict' do
          is_expected.to eq DISALLOW
        end
      end

      context 'and it is unexpected' do
        before do
          allow(service).to receive(:get_akismet_verdict).and_return("unexpected")
        end

        it 'allows' do
          is_expected.to eq ALLOW
        end
      end
    end

    context 'if more than one service returns a verdict' do
      context 'and they are supported' do
        before do
          allow(service).to receive(:get_akismet_verdict).and_return(DISALLOW)
          allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
        end

        it 'renders the more restrictive verdict' do
          is_expected.to eq BLOCK_USER
        end
      end

      context 'and one is supported' do
        before do
          allow(service).to receive(:get_akismet_verdict).and_return('nonsense')
          allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
        end

        it 'renders the more restrictive verdict' do
          is_expected.to eq BLOCK_USER
        end
      end

      context 'and none are supported' do
        before do
          allow(service).to receive(:get_akismet_verdict).and_return('nonsense')
          allow(service).to receive(:get_spamcheck_verdict).and_return('rubbish')
        end

        it 'renders the more restrictive verdict' do
          is_expected.to eq ALLOW
        end
      end
    end

    context 'if allow_possible_spam application setting is true' do
      before do
        stub_application_setting(allow_possible_spam: true)
      end

      context 'and a service returns a verdict that should be overridden' do
        before do
          allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
        end

        it 'overrides and renders the override verdict' do
          is_expected.to eq OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM
        end
      end

      context 'and a service returns a verdict that does not need to be overridden' do
        before do
          allow(service).to receive(:get_spamcheck_verdict).and_return(ALLOW)
        end

        it 'does not override and renders the original verdict' do
          is_expected.to eq ALLOW
        end
      end
    end

    context 'if user is trusted to create possible spam' do
      before do
        user.custom_attributes.create!(key: 'trusted_by', value: 'does not matter')
      end

      context 'and a service returns a verdict that should be overridden' do
        before do
          allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
        end

        it 'overrides and renders the override verdict' do
          is_expected.to eq OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM
        end
      end

      context 'and a service returns a verdict that does not need to be overridden' do
        before do
          allow(service).to receive(:get_spamcheck_verdict).and_return(ALLOW)
        end

        it 'does not override and renders the original verdict' do
          is_expected.to eq ALLOW
        end
      end
    end

    context 'records metrics' do
      let(:histogram) { instance_double(Prometheus::Client::Histogram) }

      using RSpec::Parameterized::TableSyntax

      where(:verdict, :label) do
        Spam::SpamConstants::ALLOW             |  'ALLOW'
        Spam::SpamConstants::CONDITIONAL_ALLOW |  'CONDITIONAL_ALLOW'
        Spam::SpamConstants::BLOCK_USER        |  'BLOCK'
        Spam::SpamConstants::DISALLOW          |  'DISALLOW'
        Spam::SpamConstants::NOOP              |  'NOOP'
      end

      with_them do
        before do
          allow(Gitlab::Metrics).to receive(:histogram).with(:gitlab_spamcheck_request_duration_seconds, anything).and_return(histogram)
          allow(service).to receive(:get_spamcheck_verdict).and_return(verdict)
        end

        it 'records duration with labels' do
          expect(histogram).to receive(:observe).with(a_hash_including(result: label), anything)
          execute
        end
      end
    end
  end

  shared_examples 'akismet verdict' do
    let(:target) { issue }

    subject(:get_akismet_verdict) { service.send(:get_akismet_verdict) }

    context 'if Akismet is enabled' do
      before do
        stub_application_setting(akismet_enabled: true)
        allow_next_instance_of(Spam::AkismetService) do |service|
          allow(service).to receive(:spam?).and_return(akismet_result)
        end
      end

      context 'if Akismet considers it spam' do
        let(:akismet_result) { true }

        context 'if reCAPTCHA is enabled' do
          before do
            stub_application_setting(recaptcha_enabled: true)
          end

          it 'returns conditionally allow verdict' do
            is_expected.to eq CONDITIONAL_ALLOW
          end
        end

        context 'if reCAPTCHA is not enabled' do
          before do
            stub_application_setting(recaptcha_enabled: false)
          end

          it 'renders disallow verdict' do
            is_expected.to eq DISALLOW
          end
        end
      end

      context 'if Akismet does not consider it spam' do
        let(:akismet_result) { false }

        it 'renders allow verdict' do
          is_expected.to eq ALLOW
        end
      end
    end

    context 'if Akismet is not enabled' do
      before do
        stub_application_setting(akismet_enabled: false)
      end

      it 'renders allow verdict' do
        is_expected.to eq ALLOW
      end
    end
  end

  shared_examples 'spamcheck verdict' do
    subject(:get_spamcheck_verdict) { service.send(:get_spamcheck_verdict) }

    context 'if a Spam Check endpoint enabled and set to a URL' do
      let(:spam_check_body) { {} }
      let(:endpoint_url) { "grpc://www.spamcheckurl.com/spam_check" }

      let(:spam_client) do
        Gitlab::Spamcheck::Client.new
      end

      before do
        stub_application_setting(spam_check_endpoint_enabled: true)
        stub_application_setting(spam_check_endpoint_url: endpoint_url)
      end

      context 'if the endpoint is accessible' do
        let(:user_scores) { Abuse::UserTrustScore.new(user) }

        before do
          allow(service).to receive(:spamcheck_client).and_return(spam_client)
          allow(spam_client).to receive(:spam?).and_return(spam_client_result)
        end

        context 'if the result is a NOOP verdict' do
          let(:verdict_evaluated) { false }
          let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::NOOP }

          it 'returns the verdict' do
            is_expected.to eq(NOOP)
            expect(user_scores.spam_score).to eq(0.0)
          end
        end

        context 'the result is a valid verdict' do
          let(:verdict_score) { 0.05 }
          let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::ALLOW }

          context 'the result was evaluated' do
            it 'returns the verdict and updates the spam score' do
              is_expected.to eq(ALLOW)
              expect(user_scores.spam_score).to be_within(0.000001).of(verdict_score)
            end
          end

          context 'the result was not evaluated' do
            let(:verdict_evaluated) { false }

            it 'returns the verdict and does not update the spam score' do
              expect(subject).to eq(ALLOW)
              expect(user_scores.spam_score).to eq(0.0)
            end
          end
        end

        context 'when recaptcha is enabled' do
          before do
            allow(Gitlab::Recaptcha).to receive(:enabled?).and_return(true)
          end

          using RSpec::Parameterized::TableSyntax

          where(:verdict_value, :expected, :verdict_score) do
            ::Spamcheck::SpamVerdict::Verdict::ALLOW              | ::Spam::SpamConstants::ALLOW              | 0.1
            ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW  | ::Spam::SpamConstants::CONDITIONAL_ALLOW  | 0.5
            ::Spamcheck::SpamVerdict::Verdict::DISALLOW           | ::Spam::SpamConstants::DISALLOW           | 0.8
            ::Spamcheck::SpamVerdict::Verdict::BLOCK              | ::Spam::SpamConstants::BLOCK_USER         | 0.9
          end

          with_them do
            it "returns expected spam constant and updates the spam score" do
              is_expected.to eq(expected)
              expect(user_scores.spam_score).to be_within(0.000001).of(verdict_score)
            end
          end
        end

        context 'when recaptcha is disabled' do
          before do
            allow(Gitlab::Recaptcha).to receive(:enabled?).and_return(false)
          end

          using RSpec::Parameterized::TableSyntax

          where(:verdict_value, :expected) do
            ::Spamcheck::SpamVerdict::Verdict::ALLOW              | ::Spam::SpamConstants::ALLOW
            ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW  | ::Spam::SpamConstants::CONDITIONAL_ALLOW
            ::Spamcheck::SpamVerdict::Verdict::DISALLOW           | ::Spam::SpamConstants::DISALLOW
            ::Spamcheck::SpamVerdict::Verdict::BLOCK              | ::Spam::SpamConstants::BLOCK_USER
          end

          with_them do
            it "returns expected spam constant" do
              is_expected.to eq(expected)
            end
          end
        end

        context 'the requested is aborted' do
          before do
            allow(spam_client).to receive(:spam?).and_raise(GRPC::Aborted)
          end

          it 'returns nil' do
            expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
              an_instance_of(GRPC::Aborted), error: ::Spam::SpamConstants::ERROR_TYPE
            )
            is_expected.to be_nil
          end
        end

        context 'if the endpoint times out' do
          before do
            allow(spam_client).to receive(:spam?).and_raise(GRPC::DeadlineExceeded)
          end

          it 'returns nil' do
            expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
              an_instance_of(GRPC::DeadlineExceeded), error: ::Spam::SpamConstants::ERROR_TYPE
            )
            is_expected.to be_nil
          end
        end
      end
    end

    context 'if a Spam Check endpoint is not set' do
      before do
        stub_application_setting(spam_check_endpoint_url: nil)
      end

      it 'returns nil' do
        is_expected.to be_nil
      end
    end

    context 'if Spam Check endpoint is not enabled' do
      before do
        stub_application_setting(spam_check_endpoint_enabled: false)
      end

      it 'returns nil' do
        is_expected.to be_nil
      end
    end
  end

  describe '#execute' do
    describe 'issue' do
      let(:target) { issue }

      context 'when issue is publicly visible' do
        before do
          allow(issue).to receive(:publicly_visible?).and_return(true)
        end

        it_behaves_like 'execute spam verdict service'
      end

      context 'when issue is not publicly visible' do
        before do
          allow(issue).to receive(:publicly_visible?).and_return(false)
          allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
        end

        it 'overrides and renders the override verdict' do
          expect(service.execute).to eq OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM
        end
      end
    end

    describe 'snippet' do
      let(:target) { snippet }

      it_behaves_like 'execute spam verdict service'
    end
  end

  describe '#get_akismet_verdict' do
    describe 'issue' do
      let(:target) { issue }

      it_behaves_like 'akismet verdict'
    end

    describe 'snippet' do
      let(:target) { snippet }

      it_behaves_like 'akismet verdict'
    end
  end

  describe '#get_spamcheck_verdict' do
    describe 'issue' do
      let(:target) { issue }

      it_behaves_like 'spamcheck verdict'
    end

    describe 'snippet' do
      let(:target) { snippet }

      it_behaves_like 'spamcheck verdict'
    end
  end
end


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

module Spam
  class AkismetMarkAsSpamService
    include ::AkismetMethods

    attr_accessor :target, :options

    def initialize(target:)
      @target = target
      @options = {}
    end

    def execute
      @options[:ip_address] = @target.ip_address
      @options[:user_agent] = @target.user_agent

      return unless target.submittable_as_spam?
      return unless akismet.submit_spam

      target.user_agent_detail.update_attribute(:submitted, true)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Spam::AkismetMarkAsSpamService, feature_category: :instance_resiliency do
  let(:user_agent_detail) { build(:user_agent_detail) }
  let(:spammable) { build(:issue, user_agent_detail: user_agent_detail) }
  let(:fake_akismet_service) { double(:akismet_service, submit_spam: true) }

  subject { described_class.new(target: spammable) }

  describe '#execute' do
    before do
      allow(subject).to receive(:akismet).and_return(fake_akismet_service)
    end

    context 'when the spammable object is not submittable' do
      before do
        allow(spammable).to receive(:submittable_as_spam?).and_return false
      end

      it 'does not submit as spam' do
        expect(subject.execute).to be_falsey
      end
    end

    context 'spam is submitted successfully' do
      before do
        allow(spammable).to receive(:submittable_as_spam?).and_return true
        allow(fake_akismet_service).to receive(:submit_spam).and_return true
      end

      it 'submits as spam' do
        expect(subject.execute).to be_truthy
      end

      it "updates the spammable object's user agent detail as being submitted as spam" do
        expect(user_agent_detail).to receive(:update_attribute)

        subject.execute
      end

      context 'when Akismet does not consider it spam' do
        it 'does not update the spammable object as spam' do
          allow(fake_akismet_service).to receive(:submit_spam).and_return false

          expect(subject.execute).to be_falsey
        end
      end
    end
  end
end


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

module Spam
  ##
  # This class is a Parameter Object (https://refactoring.com/catalog/introduceParameterObject.html)
  # which acts as an container abstraction for multiple values related to spam and
  # captcha processing for a provided HTTP request object.
  #
  # It is used to encapsulate these values and allow them to be passed from the Controller/GraphQL
  # layers down into to the Service layer, without needing to pass the entire request and therefore
  # unnecessarily couple the Service layer to the HTTP request.
  #
  # Values contained are:
  #
  # captcha_response: The response resulting from the user solving a captcha.  Currently it is
  #   a scalar reCAPTCHA response string, but it can be expanded to an object in the future to
  #   support other captcha implementations such as FriendlyCaptcha. Obtained from
  #   request.headers['X-GitLab-Captcha-Response']
  # spam_log_id: The id of a SpamLog record. Obtained from request.headers['X-GitLab-Spam-Log-Id']
  # ip_address = The remote IP. Obtained from request.env['action_dispatch.remote_ip']
  # user_agent = The user agent. Obtained from request.env['HTTP_USER_AGENT']
  # referer = The HTTP referer. Obtained from request.env['HTTP_REFERER']
  #
  # NOTE: The presence of these values in the request is not currently enforced. If they are missing,
  #      then the spam check may fail, or the SpamLog or UserAgentDetail may have missing fields.
  class SpamParams
    def self.new_from_request(request:)
      self.normalize_grape_request_headers(request: request)
      self.new(
        captcha_response: request.headers['X-GitLab-Captcha-Response'],
        spam_log_id: request.headers['X-GitLab-Spam-Log-Id'],
        ip_address: request.env['action_dispatch.remote_ip'].to_s,
        user_agent: request.env['HTTP_USER_AGENT'],
        referer: request.env['HTTP_REFERER']
      )
    end

    attr_reader :captcha_response, :spam_log_id, :ip_address, :user_agent, :referer

    def initialize(captcha_response:, spam_log_id:, ip_address:, user_agent:, referer:)
      @captcha_response = captcha_response
      @spam_log_id = spam_log_id
      @ip_address = ip_address
      @user_agent = user_agent
      @referer = referer
    end

    def ==(other)
      other.class <= self.class &&
        other.captcha_response == captcha_response &&
        other.spam_log_id == spam_log_id &&
        other.ip_address == ip_address &&
        other.user_agent == user_agent &&
        other.referer == referer
    end

    def self.normalize_grape_request_headers(request:)
      # If needed, make a normalized copy of Grape headers with the case of 'GitLab' (with an
      # uppercase 'L') instead of 'Gitlab' (with a lowercase 'l'), because Grape header helper keys
      # are "coerced into a capitalized kebab case". See https://github.com/ruby-grape/grape#request
      %w[X-Gitlab-Captcha-Response X-Gitlab-Spam-Log-Id].each do |header|
        request.headers[header.gsub('Gitlab', 'GitLab')] = request.headers[header] if request.headers.key?(header)
      end
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Spam::SpamParams, feature_category: :instance_resiliency do
  shared_examples 'constructs from a request' do
    it 'constructs from a request' do
      expected = ::Spam::SpamParams.new(
        captcha_response: captcha_response,
        spam_log_id: spam_log_id,
        ip_address: ip_address,
        user_agent: user_agent,
        referer: referer
      )
      expect(described_class.new_from_request(request: request)).to eq(expected)
    end
  end

  describe '.new_from_request' do
    let(:captcha_response) { 'abc123' }
    let(:spam_log_id) { 42 }
    let(:ip_address) { '0.0.0.0' }
    let(:user_agent) { 'Lynx' }
    let(:referer) { 'http://localhost' }

    let(:env) do
      {
        'action_dispatch.remote_ip' => ip_address,
        'HTTP_USER_AGENT' => user_agent,
        'HTTP_REFERER' => referer
      }
    end

    let(:request) { double(:request, headers: headers, env: env) }

    context 'with a normal Rails request' do
      let(:headers) do
        {
          'X-GitLab-Captcha-Response' => captcha_response,
          'X-GitLab-Spam-Log-Id' => spam_log_id
        }
      end

      it_behaves_like 'constructs from a request'
    end

    context 'with a grape request' do
      let(:headers) do
        {
          'X-Gitlab-Captcha-Response' => captcha_response,
          'X-Gitlab-Spam-Log-Id' => spam_log_id
        }
      end

      it_behaves_like 'constructs from a request'
    end
  end
end


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

module Releases
  class CreateEvidenceService
    def initialize(release, pipeline: nil)
      @release = release
      @pipeline = pipeline
    end

    def execute
      evidence = release.evidences.build

      summary = ::Evidences::EvidenceSerializer.new.represent(evidence, evidence_options) # rubocop: disable CodeReuse/Serializer
      evidence.summary = summary
      # TODO: fix the sha generation https://gitlab.com/groups/gitlab-org/-/epics/3683
      evidence.summary_sha = Gitlab::CryptoHelper.sha256(summary)

      evidence.save!
    end

    private

    attr_reader :release, :pipeline

    def evidence_options
      {}
    end
  end
end

Releases::CreateEvidenceService.prepend_mod_with('Releases::CreateEvidenceService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Releases::CreateEvidenceService, feature_category: :release_orchestration do
  let_it_be(:project) { create(:project) }

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

  it 'creates evidence' do
    expect { service.execute }.to change { release.reload.evidences.count }.by(1)
  end

  it 'saves evidence summary' do
    service.execute
    evidence = Releases::Evidence.last

    expect(release.tag).not_to be_nil
    expect(evidence.summary["release"]["tag_name"]).to eq(release.tag)
  end

  it 'saves sha' do
    service.execute
    evidence = Releases::Evidence.last

    expect(evidence.summary_sha).not_to be_nil
  end
end


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

module Releases
  class DestroyService < Releases::BaseService
    def execute
      return error(_('Release does not exist'), 404) unless release
      return error(_('Access Denied'), 403) unless allowed?

      if release.destroy
        update_catalog_resource!

        execute_hooks(release, 'delete')

        audit(release, action: :deleted)

        success(tag: existing_tag, release: release)
      else
        error(release.errors.messages || '400 Bad request', 400)
      end
    end

    private

    def update_catalog_resource!
      return unless project.catalog_resource

      return unless project.catalog_resource.versions.none?

      project.catalog_resource.update!(state: 'draft')
    end

    def allowed?
      Ability.allowed?(current_user, :destroy_release, release)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Releases::DestroyService, feature_category: :release_orchestration do
  let(:project) { create(:project, :repository) }
  let(:mainatiner) { create(:user) }
  let(:repoter) { create(:user) }
  let(:tag) { 'v1.1.0' }
  let!(:release) { create(:release, project: project, tag: tag) }
  let(:service) { described_class.new(project, user, params) }
  let(:params) { { tag: tag } }
  let(:user) { mainatiner }

  before do
    project.add_maintainer(mainatiner)
    project.add_reporter(repoter)
  end

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

    context 'when there is a release' do
      it 'removes the release' do
        expect { subject }.to change { project.releases.count }.by(-1)
      end

      it 'returns the destroyed object' do
        is_expected.to include(status: :success, release: release)
      end

      context 'when the release is for a catalog resource' do
        let!(:catalog_resource) { create(:ci_catalog_resource, :published, project: project) }
        let!(:version) { create(:ci_catalog_resource_version, catalog_resource: catalog_resource, release: release) }

        it 'does not update the catalog resources if there are still releases' do
          second_release = create(:release, project: project, tag: 'v1.2.0')
          create(:ci_catalog_resource_version, catalog_resource: catalog_resource, release: second_release)

          subject

          expect(catalog_resource.reload.state).to eq('published')
        end

        it 'updates the catalog resource if there are no more releases' do
          subject

          expect(catalog_resource.reload.state).to eq('draft')
        end
      end
    end

    context 'when tag does not exist in the repository' do
      let(:tag) { 'v1.1.1' }

      it 'removes the orphaned release' do
        expect { subject }.to change { project.releases.count }.by(-1)
      end
    end

    context 'when release is not found' do
      let!(:release) {}

      it 'returns an error' do
        is_expected.to include(status: :error, message: 'Release does not exist', http_status: 404)
      end
    end

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

      it 'returns an error' do
        is_expected.to include(status: :error, message: 'Access Denied', http_status: 403)
      end
    end

    context 'when a milestone is tied to the release' do
      let!(:milestone) { create(:milestone, :active, project: project, title: 'v1.0') }
      let!(:release) { create(:release, milestones: [milestone], project: project, tag: tag) }

      it 'destroys the release but leave the milestone intact' do
        expect { subject }.not_to change { Milestone.count }
        expect(milestone.reload).to be_persisted
      end
    end

    it 'executes hooks' do
      expect(service.release).to receive(:execute_hooks).with('delete')

      service.execute
    end
  end
end


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

module Releases
  class UpdateService < Releases::BaseService
    def execute
      if error = validate
        return error
      end

      if param_for_milestones_provided?
        previous_milestones = release.milestones.map(&:id)
        params[:milestones] = milestones
      end

      # transaction needed as Rails applies `save!` to milestone_releases
      # when it does assign_attributes instead of actual saving
      # this leads to the validation error being raised
      # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43385
      ApplicationRecord.transaction do
        if release.update(params)
          execute_hooks(release, 'update')
          audit(release, action: :updated)
          audit(release, action: :milestones_updated) if milestones_updated?(previous_milestones)
          success(tag: existing_tag, release: release, milestones_updated: milestones_updated?(previous_milestones))
        else
          error(release.errors.messages || '400 Bad request', 400)
        end
      rescue ActiveRecord::RecordInvalid => e
        error(e.message || '400 Bad request', 400)
      end
    end

    private

    def validate
      return error(_('Tag does not exist'), 404) unless existing_tag
      return error(_('Release does not exist'), 404) unless release
      return error(_('Access Denied'), 403) unless allowed?
      return error(_('params is empty'), 400) if empty_params?
      return error(format(_("Milestone(s) not found: %{milestones}"), milestones: inexistent_milestone_titles.join(', ')), 400) if inexistent_milestone_titles.any? # rubocop:disable Layout/LineLength
      return error(format(_("Milestone id(s) not found: %{milestones}"), milestones: inexistent_milestone_ids.join(', ')), 400) if inexistent_milestone_ids.any? # rubocop:disable Layout/LineLength
    end

    def allowed?
      Ability.allowed?(current_user, :update_release, release)
    end

    def empty_params?
      params.except(:tag).empty?
    end

    def milestones_updated?(previous_milestones)
      return false unless param_for_milestones_provided?

      previous_milestones.to_set != release.milestones.map(&:id)
    end
  end
end

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Releases::UpdateService, feature_category: :continuous_integration do
  let(:project) { create(:project, :repository) }
  let(:user) { create(:user) }
  let(:new_name) { 'A new name' }
  let(:new_description) { 'The best release!' }
  let(:params) { { name: new_name, description: new_description, tag: tag_name } }
  let(:service) { described_class.new(project, user, params) }
  let!(:release) { create(:release, project: project, author: user, tag: tag_name) }
  let(:tag_name) { 'v1.1.0' }

  before do
    project.add_developer(user)
  end

  describe '#execute' do
    shared_examples 'a failed update' do
      it 'raises an error' do
        result = service.execute
        expect(result[:status]).to eq(:error)
        expect(result[:milestones_updated]).to be_falsy
      end
    end

    it 'successfully updates an existing release' do
      result = service.execute
      expect(result[:status]).to eq(:success)
      expect(result[:release].name).to eq(new_name)
      expect(result[:release].description).to eq(new_description)
    end

    it 'executes hooks' do
      expect(service.release).to receive(:execute_hooks).with('update')

      service.execute
    end

    context 'when the tag does not exists' do
      let(:tag_name) { 'foobar' }

      it_behaves_like 'a failed update'
    end

    context 'when the release does not exist' do
      let!(:release) {}

      it_behaves_like 'a failed update'
    end

    context 'when a milestone is passed in' do
      let(:milestone) { create(:milestone, project: project, title: 'v1.0') }
      let(:params_with_milestone) { params.merge!({ milestones: [new_title] }) }
      let(:new_milestone) { create(:milestone, project: project, title: new_title) }
      let(:service) { described_class.new(new_milestone.project, user, params_with_milestone) }

      before do
        release.milestones << milestone
      end

      shared_examples 'updates milestones' do
        it 'updates the related milestone accordingly' do
          release.reload
          result = service.execute

          expect(release.milestones.first.title).to eq(new_title)
          expect(result[:milestones_updated]).to be_truthy
        end
      end

      context 'a different milestone' do
        let(:new_title) { 'v2.0' }

        it_behaves_like 'updates milestones'
      end

      context 'an identical milestone' do
        let(:new_title) { 'v1.0' }

        it "raises an error" do
          expect { service.execute }.to raise_error(ActiveRecord::RecordInvalid)
        end
      end

      context 'by ids' do
        let(:new_title) { 'v2.0' }
        let(:params_with_milestone) { params.merge!({ milestone_ids: [new_milestone.id] }) }

        it_behaves_like 'updates milestones'
      end
    end

    context "when an 'empty' milestone is passed in" do
      let(:milestone) { create(:milestone, project: project, title: 'v1.0') }

      before do
        release.milestones << milestone

        service.params = params_with_empty_milestone
      end

      shared_examples 'removes milestones' do
        it 'removes the old milestone and does not associate any new milestone' do
          result = service.execute
          release.reload

          expect(release.milestones).not_to be_present
          expect(result[:milestones_updated]).to be_truthy
        end
      end

      context 'by title' do
        let(:params_with_empty_milestone) { params.merge!({ milestones: [] }) }

        it_behaves_like 'removes milestones'
      end

      context 'by id' do
        let(:params_with_empty_milestone) { params.merge!({ milestone_ids: [] }) }

        it_behaves_like 'removes milestones'
      end
    end

    context "when multiple new milestones are passed in" do
      let(:new_title_1) { 'v2.0' }
      let(:new_title_2) { 'v2.0-rc' }
      let(:milestone) { create(:milestone, project: project, title: 'v1.0') }
      let(:service) { described_class.new(project, user, params_with_milestones) }
      let!(:new_milestone_1) { create(:milestone, project: project, title: new_title_1) }
      let!(:new_milestone_2) { create(:milestone, project: project, title: new_title_2) }

      before do
        release.milestones << milestone
      end

      shared_examples 'updates multiple milestones' do
        it 'removes the old milestone and update the release with the new ones' do
          result = service.execute
          release.reload

          milestone_titles = release.milestones.map(&:title)
          expect(milestone_titles).to match_array([new_title_1, new_title_2])
          expect(result[:milestones_updated]).to be_truthy
        end
      end

      context 'by title' do
        let(:params_with_milestones) { params.merge!({ milestones: [new_title_1, new_title_2] }) }

        it_behaves_like 'updates multiple milestones'
      end

      context 'by id' do
        let(:params_with_milestones) { params.merge!({ milestone_ids: [new_milestone_1.id, new_milestone_2.id] }) }

        it_behaves_like 'updates multiple milestones'
      end
    end
  end
end