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

# TodoService class
#
# Used for creating/updating todos after certain user actions
#
# Ex.
#   TodoService.new.new_issue(issue, current_user)
#
class TodoService
  include Gitlab::Utils::UsageData

  # When create an issue we should:
  #
  #  * create a todo for assignee if issue is assigned
  #  * create a todo for each mentioned user on issue
  #
  def new_issue(issue, current_user)
    new_issuable(issue, current_user)
  end

  # When update an issue we should:
  #
  #  * mark all pending todos related to the issue for the current user as done
  #
  def update_issue(issue, current_user, skip_users = [])
    update_issuable(issue, current_user, skip_users)
  end

  # When close an issue we should:
  #
  #  * mark all pending todos related to the target for the current user as done
  #
  def close_issue(issue, current_user)
    resolve_todos_for_target(issue, current_user)
  end

  # When we destroy a todo target we should:
  #
  #  * refresh the todos count cache for all users with todos on the target
  #
  # This needs to yield back to the caller to destroy the target, because it
  # collects the todo users before the todos themselves are deleted, then
  # updates the todo counts for those users.
  #
  def destroy_target(target)
    todo_user_ids = target.todos.distinct_user_ids

    yield target

    Users::UpdateTodoCountCacheService.new(todo_user_ids).execute if todo_user_ids.present?
  end

  # When we reassign an assignable object (issuable, alert) we should:
  #
  #  * create a pending todo for new assignee if object is assigned
  #
  def reassigned_assignable(issuable, current_user, old_assignees = [])
    create_assignment_todo(issuable, current_user, old_assignees)
  end

  # When we reassign an reviewable object (merge request) we should:
  #
  #  * create a pending todo for new reviewer if object is assigned
  #
  def reassigned_reviewable(issuable, current_user, old_reviewers = [])
    create_reviewer_todo(issuable, current_user, old_reviewers)
  end

  # When create a merge request we should:
  #
  #  * creates a pending todo for assignee if merge request is assigned
  #  * create a todo for each mentioned user on merge request
  #
  def new_merge_request(merge_request, current_user)
    new_issuable(merge_request, current_user)
  end

  # When update a merge request we should:
  #
  #  * create a todo for each mentioned user on merge request
  #
  def update_merge_request(merge_request, current_user, skip_users = [])
    update_issuable(merge_request, current_user, skip_users)
  end

  # When close a merge request we should:
  #
  #  * mark all pending todos related to the target for the current user as done
  #
  def close_merge_request(merge_request, current_user)
    resolve_todos_for_target(merge_request, current_user)
  end

  # When merge a merge request we should:
  #
  #  * mark all pending todos related to the target for the current user as done
  #
  def merge_merge_request(merge_request, current_user)
    resolve_todos_for_target(merge_request, current_user)
  end

  # When a build fails on the HEAD of a merge request we should:
  #
  #  * create a todo for each merge participant
  #
  def merge_request_build_failed(merge_request)
    merge_request.merge_participants.each do |user|
      create_build_failed_todo(merge_request, user)
    end
  end

  # When a new commit is pushed to a merge request we should:
  #
  #  * mark all pending todos related to the merge request for that user as done
  #
  def merge_request_push(merge_request, current_user)
    resolve_todos_for_target(merge_request, current_user)
  end

  # When a build is retried to a merge request we should:
  #
  #  * mark all pending todos related to the merge request as done for each merge participant
  #
  def merge_request_build_retried(merge_request)
    merge_request.merge_participants.each do |user|
      resolve_todos_for_target(merge_request, user)
    end
  end

  # When a merge request could not be merged due to its unmergeable state we should:
  #
  #  * create a todo for each merge participant
  #
  def merge_request_became_unmergeable(merge_request)
    merge_request.merge_participants.each do |user|
      create_unmergeable_todo(merge_request, user)
    end
  end

  # When create a note we should:
  #
  #  * mark all pending todos related to the noteable for the note author as done
  #  * create a todo for each mentioned user on note
  #
  def new_note(note, current_user)
    handle_note(note, current_user)
  end

  # When update a note we should:
  #
  #  * mark all pending todos related to the noteable for the current user as done
  #  * create a todo for each new user mentioned on note
  #
  def update_note(note, current_user, skip_users = [])
    handle_note(note, current_user, skip_users)
  end

  # When an emoji is awarded we should:
  #
  #  * mark all pending todos related to the awardable for the current user as done
  #
  def new_award_emoji(awardable, current_user)
    resolve_todos_for_target(awardable, current_user)
  end

  # When user marks a target as todo
  def mark_todo(target, current_user)
    project = target.project
    attributes = attributes_for_todo(project, target, current_user, Todo::MARKED)
    create_todos(current_user, attributes, target_namespace(target), project)
  end

  def todo_exist?(issuable, current_user)
    TodosFinder.new(current_user).any_for_target?(issuable, :pending)
  end

  # Resolves all todos related to target for the current_user
  def resolve_todos_for_target(target, current_user)
    attributes = attributes_for_target(target)

    resolve_todos(pending_todos([current_user], attributes), current_user)
  end

  # Resolves all todos related to target for all users
  def resolve_todos_with_attributes_for_target(target, attributes, resolution: :done, resolved_by_action: :system_done)
    target_attributes = { target_id: target.id, target_type: target.class.polymorphic_name }
    attributes.merge!(target_attributes)
    attributes[:preload_user_association] = true

    todos = PendingTodosFinder.new(attributes).execute
    users = todos.map(&:user)
    todos_ids = todos.batch_update(state: resolution, resolved_by_action: resolved_by_action)
    users.each(&:update_todos_count_cache)
    todos_ids
  end

  def resolve_todos(todos, current_user, resolution: :done, resolved_by_action: :system_done)
    todos_ids = todos.batch_update(state: resolution, resolved_by_action: resolved_by_action)

    current_user.update_todos_count_cache

    todos_ids
  end

  def resolve_todo(todo, current_user, resolution: :done, resolved_by_action: :system_done)
    return if todo.done?

    todo.update(state: resolution, resolved_by_action: resolved_by_action)

    current_user.update_todos_count_cache
  end

  def resolve_access_request_todos(member)
    return if member.nil?

    # Group or Project
    target = member.source

    todos_params = {
      state: :pending,
      author_id: member.user_id,
      action: ::Todo::MEMBER_ACCESS_REQUESTED,
      type: target.class.polymorphic_name
    }

    resolve_todos_with_attributes_for_target(target, todos_params)
  end

  def restore_todos(todos, current_user)
    todos_ids = todos.batch_update(state: :pending)

    current_user.update_todos_count_cache

    todos_ids
  end

  def restore_todo(todo, current_user)
    return if todo.pending?

    todo.update(state: :pending)

    current_user.update_todos_count_cache
  end

  def create_request_review_todo(target, author, reviewers)
    project = target.project
    attributes = attributes_for_todo(project, target, author, Todo::REVIEW_REQUESTED)
    create_todos(reviewers, attributes, project.namespace, project)
  end

  def create_member_access_request_todos(member)
    source = member.source
    attributes = attributes_for_access_request_todos(source, member.user, Todo::MEMBER_ACCESS_REQUESTED)

    approvers = source.access_request_approvers_to_be_notified.map(&:user)
    return true if approvers.empty?

    if source.instance_of? Project
      project = source
      namespace = project.namespace
    else
      project = nil
      namespace = source
    end

    create_todos(approvers, attributes, namespace, project)
  end

  private

  def create_todos(users, attributes, namespace, project)
    users = Array(users)

    return if users.empty?

    users_single_todos, users_multiple_todos = users.partition { |u| Feature.disabled?(:multiple_todos, u) }
    excluded_user_ids = []

    if users_single_todos.present?
      excluded_user_ids += pending_todos(
        users_single_todos,
        attributes.slice(:project_id, :target_id, :target_type, :commit_id, :discussion)
      ).distinct_user_ids
    end

    if users_multiple_todos.present? && !Todo::ACTIONS_MULTIPLE_ALLOWED.include?(attributes.fetch(:action))
      excluded_user_ids += pending_todos(
        users_multiple_todos,
        attributes.slice(:project_id, :target_id, :target_type, :commit_id, :discussion, :action)
      ).distinct_user_ids
    end

    users.reject! { |user| excluded_user_ids.include?(user.id) }

    todos = users.map do |user|
      issue_type = attributes.delete(:issue_type)
      track_todo_creation(user, issue_type, namespace, project)

      Todo.create(attributes.merge(user_id: user.id))
    end

    Users::UpdateTodoCountCacheService.new(users.map(&:id)).execute

    todos
  end

  def new_issuable(issuable, author)
    create_assignment_todo(issuable, author)
    create_reviewer_todo(issuable, author) if issuable.allows_reviewers?
    create_mention_todos(issuable.project, issuable, author)
  end

  def update_issuable(issuable, author, skip_users = [])
    # Skip toggling a task list item in a description
    return if toggling_tasks?(issuable)

    create_mention_todos(issuable.project, issuable, author, nil, skip_users)
  end

  def toggling_tasks?(issuable)
    issuable.previous_changes.include?('description') &&
      issuable.tasks? && issuable.updated_tasks.any?
  end

  def handle_note(note, author, skip_users = [])
    return unless note.can_create_todo?

    project = note.project
    target = note.noteable

    resolve_todos_for_target(target, author)
    create_mention_todos(project, target, author, note, skip_users)
  end

  def create_assignment_todo(target, author, old_assignees = [])
    if target.assignees.any?
      project = target.project
      assignees = target.assignees - old_assignees
      attributes = attributes_for_todo(project, target, author, Todo::ASSIGNED)
      create_todos(assignees, attributes, target_namespace(target), project)
    end
  end

  def create_reviewer_todo(target, author, old_reviewers = [])
    if target.reviewers.any?
      reviewers = target.reviewers - old_reviewers
      create_request_review_todo(target, author, reviewers)
    end
  end

  def create_mention_todos(parent, target, author, note = nil, skip_users = [])
    # Create Todos for directly addressed users
    directly_addressed_users = filter_directly_addressed_users(parent, note || target, author, skip_users)
    attributes = attributes_for_todo(parent, target, author, Todo::DIRECTLY_ADDRESSED, note)
    create_todos(directly_addressed_users, attributes, parent&.namespace, parent)

    # Create Todos for mentioned users
    mentioned_users = filter_mentioned_users(parent, note || target, author, skip_users + directly_addressed_users)
    attributes = attributes_for_todo(parent, target, author, Todo::MENTIONED, note)
    create_todos(mentioned_users, attributes, parent&.namespace, parent)
  end

  def create_build_failed_todo(merge_request, todo_author)
    project = merge_request.project
    attributes = attributes_for_todo(project, merge_request, todo_author, Todo::BUILD_FAILED)
    create_todos(todo_author, attributes, project.namespace, project)
  end

  def create_unmergeable_todo(merge_request, todo_author)
    project = merge_request.project
    attributes = attributes_for_todo(project, merge_request, todo_author, Todo::UNMERGEABLE)
    create_todos(todo_author, attributes, project.namespace, project)
  end

  def attributes_for_target(target)
    attributes = {
      project_id: target&.project&.id,
      target_id: target.id,
      target_type: target.class.try(:polymorphic_name) || target.class.name,
      commit_id: nil
    }

    case target
    when Commit
      attributes.merge!(target_id: nil, commit_id: target.id)
    when Issue
      attributes[:issue_type] = target.issue_type
      attributes[:group] = target.namespace if target.project.blank?
    when Discussion
      attributes.merge!(target_type: nil, target_id: nil, discussion: target)
    end

    attributes
  end

  def attributes_for_todo(project, target, author, action, note = nil)
    attributes_for_target(target).merge!(
      project_id: project&.id,
      author_id: author.id,
      action: action,
      note: note
    )
  end

  def filter_todo_users(users, parent, target)
    reject_users_without_access(users, parent, target).uniq
  end

  def filter_mentioned_users(parent, target, author, skip_users = [])
    mentioned_users = target.mentioned_users(author) - skip_users
    filter_todo_users(mentioned_users, parent, target)
  end

  def filter_directly_addressed_users(parent, target, author, skip_users = [])
    directly_addressed_users = target.directly_addressed_users(author) - skip_users
    filter_todo_users(directly_addressed_users, parent, target)
  end

  def reject_users_without_access(users, parent, target)
    if target.respond_to?(:to_ability_name)
      select_users(users, :"read_#{target.to_ability_name}", target)
    else
      select_users(users, :read_project, parent)
    end
  end

  def select_users(users, ability, subject)
    users.select do |user|
      user.can?(ability.to_sym, subject)
    end
  end

  def pending_todos(users, criteria = {})
    PendingTodosFinder.new(criteria.merge(users: users)).execute
  end

  def track_todo_creation(user, issue_type, namespace, project)
    return unless issue_type == 'incident'

    event = "incident_management_incident_todo"
    track_usage_event(event, user.id)

    Gitlab::Tracking.event(
      self.class.to_s,
      event,
      project: project,
      namespace: namespace,
      user: user,
      label: 'redis_hll_counters.incident_management.incident_management_total_unique_counts_monthly',
      context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event).to_context]
    )
  end

  def attributes_for_access_request_todos(source, author, action, note = nil)
    attributes = {
      target_id: source.id,
      target_type: source.class.polymorphic_name,
      author_id: author.id,
      action: action,
      note: note
    }

    if source.instance_of? Project
      attributes[:project_id] = source.id
      attributes[:group_id] = source.group.id if source.group.present?
    else
      attributes[:group_id] = source.id
    end

    attributes
  end

  def target_namespace(target)
    project = target.project
    project&.namespace || target.try(:namespace)
  end
end

TodoService.prepend_mod_with('TodoService')

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe TodoService, feature_category: :team_planning do
  include AfterNextHelpers

  let_it_be(:group) { create(:group) }
  let_it_be(:project) { create(:project, :repository) }
  let_it_be(:author) { create(:user) }
  let_it_be(:assignee) { create(:user) }
  let_it_be(:non_member) { create(:user) }
  let_it_be(:member) { create(:user) }
  let_it_be(:guest) { create(:user) }
  let_it_be(:admin) { create(:admin) }
  let_it_be(:john_doe) { create(:user) }
  let_it_be(:skipped) { create(:user) }

  let(:skip_users) { [skipped] }
  let(:mentions) { 'FYI: ' + [author, assignee, john_doe, member, guest, non_member, admin, skipped].map(&:to_reference).join(' ') }
  let(:directly_addressed) { [author, assignee, john_doe, member, guest, non_member, admin, skipped].map(&:to_reference).join(' ') }
  let(:directly_addressed_and_mentioned) { member.to_reference + ", what do you think? cc: " + [guest, admin, skipped].map(&:to_reference).join(' ') }
  let(:service) { described_class.new }

  before_all do
    project.add_guest(guest)
    project.add_developer(author)
    project.add_developer(assignee)
    project.add_developer(member)
    project.add_developer(john_doe)
    project.add_developer(skipped)
  end

  shared_examples 'reassigned target' do
    let(:additional_todo_attributes) { {} }

    it 'creates a pending todo for new assignee' do
      target_unassigned.assignees = [john_doe]
      service.send(described_method, target_unassigned, author)

      should_create_todo(
        user: john_doe,
        target: target_unassigned,
        action: Todo::ASSIGNED,
        **additional_todo_attributes
      )
    end

    it 'does not create a todo if unassigned' do
      target_assigned.assignees = []

      should_not_create_any_todo { service.send(described_method, target_assigned, author) }
    end

    it 'creates a todo if new assignee is the current user' do
      target_assigned.assignees = [john_doe]
      service.send(described_method, target_assigned, john_doe)

      should_create_todo(
        user: john_doe,
        target: target_assigned,
        author: john_doe,
        action: Todo::ASSIGNED,
        **additional_todo_attributes
      )
    end

    it 'does not create a todo for guests' do
      service.send(described_method, target_assigned, author)
      should_not_create_todo(user: guest, target: target_assigned, action: Todo::MENTIONED)
    end

    it 'does not create a directly addressed todo for guests' do
      service.send(described_method, addressed_target_assigned, author)

      should_not_create_todo(user: guest, target: addressed_target_assigned, action: Todo::DIRECTLY_ADDRESSED)
    end

    it 'does not create a todo if already assigned' do
      should_not_create_any_todo { service.send(described_method, target_assigned, author, [john_doe]) }
    end
  end

  shared_examples 'reassigned reviewable target' do
    context 'with no existing reviewers' do
      let(:assigned_reviewers) { [] }

      it 'creates a pending todo for new reviewer' do
        target.reviewers = [john_doe]
        service.send(described_method, target, author)

        should_create_todo(user: john_doe, target: target, action: Todo::REVIEW_REQUESTED)
      end
    end

    context 'with an existing reviewer' do
      let(:assigned_reviewers) { [john_doe] }

      it 'does not create a todo if unassigned' do
        target.reviewers = []

        should_not_create_any_todo { service.send(described_method, target, author) }
      end

      it 'creates a todo if new reviewer is the current user' do
        target.reviewers = [john_doe]
        service.send(described_method, target, john_doe)

        should_create_todo(user: john_doe, target: target, author: john_doe, action: Todo::REVIEW_REQUESTED)
      end

      it 'does not create a todo if already assigned' do
        should_not_create_any_todo { service.send(described_method, target, author, [john_doe]) }
      end
    end
  end

  describe 'Issues' do
    let(:issue) { create(:issue, project: project, author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
    let(:addressed_issue) { create(:issue, project: project, author: author, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
    let(:assigned_issue) { create(:issue, project: project, assignees: [john_doe]) }
    let(:unassigned_issue) { create(:issue, project: project, assignees: []) }
    let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee], description: mentions) }
    let(:addressed_confident_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee], description: directly_addressed) }

    describe '#new_issue' do
      it 'creates a todo if assigned' do
        service.new_issue(assigned_issue, author)

        should_create_todo(user: john_doe, target: assigned_issue, action: Todo::ASSIGNED)
      end

      it 'does not create a todo if unassigned' do
        should_not_create_any_todo { service.new_issue(unassigned_issue, author) }
      end

      it 'creates a todo if assignee is the current user' do
        unassigned_issue.assignees = [john_doe]
        service.new_issue(unassigned_issue, john_doe)

        should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED)
      end

      it 'creates a todo for each valid mentioned user' do
        service.new_issue(issue, author)

        should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
        should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
        should_create_todo(user: author, target: issue, action: Todo::MENTIONED)
        should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
        should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
      end

      it 'creates a directly addressed todo for each valid addressed user' do
        service.new_issue(addressed_issue, author)

        should_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: guest, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
      end

      it 'creates correct todos for each valid user based on the type of mention' do
        issue.update!(description: directly_addressed_and_mentioned)

        service.new_issue(issue, author)

        should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
        should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
      end

      it 'does not create todo if user can not see the issue when issue is confidential' do
        service.new_issue(confidential_issue, john_doe)

        should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED)
        should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_not_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
      end

      it 'does not create directly addressed todo if user cannot see the issue when issue is confidential' do
        service.new_issue(addressed_confident_issue, john_doe)

        should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::ASSIGNED)
        should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
      end

      context 'when a private group is mentioned' do
        let(:group)   { create(:group, :private) }
        let(:project) { create(:project, :private, group: group) }
        let(:issue)   { create(:issue, author: author, project: project, description: group.to_reference) }

        before do
          group.add_owner(author)
          group.add_member(member, Gitlab::Access::DEVELOPER)
          group.add_member(john_doe, Gitlab::Access::DEVELOPER)

          service.new_issue(issue, author)
        end

        it 'creates a todo for group members' do
          should_create_todo(user: member, target: issue)
          should_create_todo(user: john_doe, target: issue)
        end
      end

      context 'issue is an incident' do
        let(:issue) { create(:incident, project: project, assignees: [john_doe], author: author) }

        subject do
          service.new_issue(issue, author)
          should_create_todo(user: john_doe, target: issue, action: Todo::ASSIGNED)
        end

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

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

    describe '#update_issue' do
      it 'creates a todo for each valid mentioned user not included in skip_users' do
        service.update_issue(issue, author, skip_users)

        should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
        should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
        should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
        should_create_todo(user: author, target: issue, action: Todo::MENTIONED)
        should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
        should_not_create_todo(user: skipped, target: issue, action: Todo::MENTIONED)
      end

      it 'creates a todo for each valid user not included in skip_users based on the type of mention' do
        issue.update!(description: directly_addressed_and_mentioned)

        service.update_issue(issue, author, skip_users)

        should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
        should_not_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
        should_not_create_todo(user: skipped, target: issue)
      end

      it 'creates a directly addressed todo for each valid addressed user not included in skip_users' do
        service.update_issue(addressed_issue, author, skip_users)

        should_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: guest, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: skipped, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
      end

      it 'does not create a todo if user was already mentioned and todo is pending' do
        stub_feature_flags(multiple_todos: false)

        create(:todo, :mentioned, user: member, project: project, target: issue, author: author)

        expect { service.update_issue(issue, author, skip_users) }.not_to change(member.todos, :count)
      end

      it 'does not create a todo if user was already mentioned and todo is done' do
        create(:todo, :mentioned, :done, user: skipped, project: project, target: issue, author: author)

        expect { service.update_issue(issue, author, skip_users) }.not_to change(skipped.todos, :count)
      end

      it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do
        stub_feature_flags(multiple_todos: false)

        create(:todo, :directly_addressed, user: member, project: project, target: addressed_issue, author: author)

        expect { service.update_issue(addressed_issue, author, skip_users) }.not_to change(member.todos, :count)
      end

      it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do
        create(:todo, :directly_addressed, :done, user: skipped, project: project, target: addressed_issue, author: author)

        expect { service.update_issue(addressed_issue, author, skip_users) }.not_to change(skipped.todos, :count)
      end

      it 'does not create todo if user can not see the issue when issue is confidential' do
        service.update_issue(confidential_issue, john_doe)

        should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_not_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
        should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
      end

      it 'does not create a directly addressed todo if user can not see the issue when issue is confidential' do
        service.update_issue(addressed_confident_issue, john_doe)

        should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
      end

      context 'issues with a task list' do
        it 'does not create todo when tasks are marked as completed' do
          issue.update!(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")

          service.update_issue(issue, author)

          should_not_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: assignee, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: member, target: issue, action: Todo::MENTIONED)
          should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
        end

        it 'does not create directly addressed todo when tasks are marked as completed' do
          addressed_issue.update!(description: "#{directly_addressed}\n- [x] Task 1\n- [x] Task 2\n")

          service.update_issue(addressed_issue, author)

          should_not_create_todo(user: admin, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: assignee, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
        end

        it 'does not raise an error when description not change' do
          issue.update!(title: 'Sample')

          expect { service.update_issue(issue, author) }.not_to raise_error
        end
      end
    end

    describe '#close_issue' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)

        service.close_issue(issue, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end
    end

    describe '#destroy_target' do
      it 'refreshes the todos count cache for users with todos on the target' do
        create(:todo, state: :pending, target: issue, user: author, author: author, project: issue.project)
        create(:todo, state: :done, target: issue, user: assignee, author: assignee, project: issue.project)

        expect_next(Users::UpdateTodoCountCacheService, [author.id, assignee.id]).to receive(:execute)

        service.destroy_target(issue) { issue.destroy! }
      end

      it 'yields the target to the caller' do
        expect { |b| service.destroy_target(issue, &b) }
          .to yield_with_args(issue)
      end
    end

    describe '#resolve_todos_for_target' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)

        service.resolve_todos_for_target(issue, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end

      describe 'cached counts' do
        it 'updates when todos change' do
          create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)

          expect(john_doe.todos_done_count).to eq(0)
          expect(john_doe.todos_pending_count).to eq(1)
          expect(john_doe).to receive(:update_todos_count_cache).and_call_original

          service.resolve_todos_for_target(issue, john_doe)

          expect(john_doe.todos_done_count).to eq(1)
          expect(john_doe.todos_pending_count).to eq(0)
        end
      end
    end

    describe '#resolve_todos_with_attributes_for_target' do
      it 'marks related pending todos to the target for all the users as done' do
        first_todo = create(:todo, :assigned, user: member, project: project, target: issue, author: author)
        second_todo = create(:todo, :review_requested, user: john_doe, project: project, target: issue, author: author)
        another_todo = create(:todo, :assigned, user: john_doe, project: project, target: project, author: author)

        service.resolve_todos_with_attributes_for_target(issue, {})

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
        expect(another_todo.reload).to be_pending
      end

      it 'marks related only filtered pending todos to the target for all the users as done' do
        first_todo = create(:todo, :assigned, user: member, project: project, target: issue, author: author)
        second_todo = create(:todo, :review_requested, user: john_doe, project: project, target: issue, author: author)
        another_todo = create(:todo, :assigned, user: john_doe, project: project, target: project, author: author)

        service.resolve_todos_with_attributes_for_target(issue, { action: Todo::ASSIGNED })

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_pending
        expect(another_todo.reload).to be_pending
      end

      it 'fetches the pending todos with users preloaded' do
        expect(PendingTodosFinder).to receive(:new)
          .with(a_hash_including(preload_user_association: true)).and_call_original

        service.resolve_todos_with_attributes_for_target(issue, { action: Todo::ASSIGNED })
      end
    end

    describe '#new_note' do
      let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
      let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
      let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) }
      let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
      let(:confidential_note) { create(:note, :confidential, project: project, noteable: issue, author: john_doe, note: mentions) }
      let(:addressed_note) { create(:note, project: project, noteable: issue, author: john_doe, note: directly_addressed) }
      let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
      let(:addressed_note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: directly_addressed) }
      let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: mentions) }
      let(:addressed_note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: directly_addressed) }
      let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
      let(:system_note) { create(:system_note, project: project, noteable: issue) }

      it 'mark related pending todos to the noteable for the note author as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)

        service.new_note(note, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end

      it 'does not mark related pending todos it is a system note' do
        service.new_note(system_note, john_doe)

        expect(first_todo.reload).to be_pending
        expect(second_todo.reload).to be_pending
      end

      it 'creates a todo for each valid mentioned user' do
        service.new_note(note, john_doe)

        should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
        should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
        should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
        should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
        should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
      end

      it 'creates a todo for each valid user based on the type of mention' do
        note.update!(note: directly_addressed_and_mentioned)

        service.new_note(note, john_doe)

        should_create_todo(user: member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: note)
        should_not_create_todo(user: admin, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
        should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
      end

      it 'creates a directly addressed todo for each valid addressed user' do
        service.new_note(addressed_note, john_doe)

        should_create_todo(user: member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
        should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
        should_create_todo(user: author, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
        should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
        should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
      end

      it 'does not create todo if user can not see the issue when leaving a note on a confidential issue' do
        service.new_note(note_on_confidential_issue, john_doe)

        should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
        should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
        should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
        should_not_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
        should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
        should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
      end

      it 'does not create a directly addressed todo if user can not see the issue when leaving a note on a confidential issue' do
        service.new_note(addressed_note_on_confidential_issue, john_doe)

        should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_not_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
        should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
      end

      it 'does not create todo if user can not read confidential note' do
        service.new_note(confidential_note, john_doe)

        should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
        should_not_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
        should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
        should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
        should_create_todo(user: assignee, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
        should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
      end

      context 'commits' do
        let(:base_commit_todo_attrs) { { target_id: nil, target_type: 'Commit', author: john_doe } }

        context 'leaving a note on a commit in a public project' do
          let(:project) { create(:project, :repository, :public) }

          it 'creates a todo for each valid mentioned user' do
            expected_todo = base_commit_todo_attrs.merge(
              action: Todo::MENTIONED,
              note: note_on_commit,
              commit_id: note_on_commit.commit_id
            )

            service.new_note(note_on_commit, john_doe)

            should_create_todo(expected_todo.merge(user: member))
            should_create_todo(expected_todo.merge(user: author))
            should_create_todo(expected_todo.merge(user: john_doe))
            should_create_todo(expected_todo.merge(user: guest))
            should_create_todo(expected_todo.merge(user: non_member))
          end

          it 'creates a directly addressed todo for each valid mentioned user' do
            expected_todo = base_commit_todo_attrs.merge(
              action: Todo::DIRECTLY_ADDRESSED,
              note: addressed_note_on_commit,
              commit_id: addressed_note_on_commit.commit_id
            )

            service.new_note(addressed_note_on_commit, john_doe)

            should_create_todo(expected_todo.merge(user: member))
            should_create_todo(expected_todo.merge(user: author))
            should_create_todo(expected_todo.merge(user: john_doe))
            should_create_todo(expected_todo.merge(user: guest))
            should_create_todo(expected_todo.merge(user: non_member))
          end
        end

        context 'leaving a note on a commit in a public project with private code' do
          let_it_be(:project) { create(:project, :repository, :public, :repository_private) }

          before_all do
            project.add_guest(guest)
            project.add_developer(author)
            project.add_developer(assignee)
            project.add_developer(member)
            project.add_developer(john_doe)
            project.add_developer(skipped)
          end

          it 'creates a todo for each valid mentioned user' do
            expected_todo = base_commit_todo_attrs.merge(
              action: Todo::MENTIONED,
              note: note_on_commit,
              commit_id: note_on_commit.commit_id
            )

            service.new_note(note_on_commit, john_doe)

            should_create_todo(expected_todo.merge(user: member))
            should_create_todo(expected_todo.merge(user: author))
            should_create_todo(expected_todo.merge(user: john_doe))
            should_create_todo(expected_todo.merge(user: guest))
            should_not_create_todo(expected_todo.merge(user: non_member))
          end

          it 'creates a directly addressed todo for each valid mentioned user' do
            expected_todo = base_commit_todo_attrs.merge(
              action: Todo::DIRECTLY_ADDRESSED,
              note: addressed_note_on_commit,
              commit_id: addressed_note_on_commit.commit_id
            )

            service.new_note(addressed_note_on_commit, john_doe)

            should_create_todo(expected_todo.merge(user: member))
            should_create_todo(expected_todo.merge(user: author))
            should_create_todo(expected_todo.merge(user: john_doe))
            should_create_todo(expected_todo.merge(user: guest))
            should_not_create_todo(expected_todo.merge(user: non_member))
          end
        end

        context 'leaving a note on a commit in a private project' do
          let_it_be(:project) { create(:project, :repository, :private) }

          before_all do
            project.add_guest(guest)
            project.add_developer(author)
            project.add_developer(assignee)
            project.add_developer(member)
            project.add_developer(john_doe)
            project.add_developer(skipped)
          end

          it 'creates a todo for each valid mentioned user' do
            expected_todo = base_commit_todo_attrs.merge(
              action: Todo::MENTIONED,
              note: note_on_commit,
              commit_id: note_on_commit.commit_id
            )

            service.new_note(note_on_commit, john_doe)

            should_create_todo(expected_todo.merge(user: member))
            should_create_todo(expected_todo.merge(user: author))
            should_create_todo(expected_todo.merge(user: john_doe))
            should_not_create_todo(expected_todo.merge(user: guest))
            should_not_create_todo(expected_todo.merge(user: non_member))
          end

          it 'creates a directly addressed todo for each valid mentioned user' do
            expected_todo = base_commit_todo_attrs.merge(
              action: Todo::DIRECTLY_ADDRESSED,
              note: addressed_note_on_commit,
              commit_id: addressed_note_on_commit.commit_id
            )

            service.new_note(addressed_note_on_commit, john_doe)

            should_create_todo(expected_todo.merge(user: member))
            should_create_todo(expected_todo.merge(user: author))
            should_create_todo(expected_todo.merge(user: john_doe))
            should_not_create_todo(expected_todo.merge(user: guest))
            should_not_create_todo(expected_todo.merge(user: non_member))
          end
        end
      end

      it 'does not create todo when leaving a note on snippet' do
        should_not_create_any_todo { service.new_note(note_on_project_snippet, john_doe) }
      end
    end

    describe '#mark_todo' do
      it 'creates a todo from an issue' do
        service.mark_todo(unassigned_issue, author)

        should_create_todo(user: author, target: unassigned_issue, action: Todo::MARKED)
      end

      context 'when issue belongs to a group' do
        it 'creates a todo from an issue' do
          group_issue = create(:issue, :group_level, namespace: group)
          service.mark_todo(group_issue, group_issue.author)

          should_create_todo(
            user: group_issue.author,
            author: group_issue.author,
            target: group_issue,
            action: Todo::MARKED,
            project: nil,
            group: group
          )
        end
      end
    end

    describe '#todo_exists?' do
      it 'returns false when no todo exist for the given issuable' do
        expect(service.todo_exist?(unassigned_issue, author)).to be_falsy
      end

      it 'returns true when a todo exist for the given issuable' do
        service.mark_todo(unassigned_issue, author)

        expect(service.todo_exist?(unassigned_issue, author)).to be_truthy
      end
    end

    context 'when multiple_todos are enabled' do
      before do
        stub_feature_flags(multiple_todos: true)
      end

      it 'creates a MENTIONED todo even if user already has a pending MENTIONED todo' do
        create(:todo, :mentioned, user: member, project: project, target: issue, author: author)

        expect { service.update_issue(issue, author) }.to change(member.todos, :count)
      end

      it 'creates a DIRECTLY_ADDRESSED todo even if user already has a pending DIRECTLY_ADDRESSED todo' do
        create(:todo, :directly_addressed, user: member, project: project, target: issue, author: author)

        issue.update!(description: "#{member.to_reference}, what do you think?")

        expect { service.update_issue(issue, author) }.to change(member.todos, :count)
      end

      it 'creates an ASSIGNED todo even if user already has a pending MARKED todo' do
        create(:todo, :marked, user: john_doe, project: project, target: assigned_issue, author: author)

        expect { service.reassigned_assignable(assigned_issue, author) }.to change(john_doe.todos, :count)
      end

      it 'does not create an ASSIGNED todo if user already has an ASSIGNED todo' do
        create(:todo, :assigned, user: john_doe, project: project, target: assigned_issue, author: author)

        expect { service.reassigned_assignable(assigned_issue, author) }.not_to change(john_doe.todos, :count)
      end

      it 'creates multiple todos if a user is assigned and mentioned in a new issue' do
        assigned_issue.description = mentions
        service.new_issue(assigned_issue, author)

        should_create_todo(user: john_doe, target: assigned_issue, action: Todo::ASSIGNED)
        should_create_todo(user: john_doe, target: assigned_issue, action: Todo::MENTIONED)
      end
    end
  end

  describe 'Work Items' do
    let_it_be(:work_item) { create(:work_item, :task, project: project, author: author) }

    describe '#mark_todo' do
      it 'creates a todo from a work item' do
        service.mark_todo(work_item, author)

        should_create_todo(user: author, target: work_item, action: Todo::MARKED)
      end

      context 'when work item belongs to a group' do
        it 'creates a todo from a work item' do
          group_work_item = create(:work_item, :group_level, namespace: group)
          service.mark_todo(group_work_item, group_work_item.author)

          should_create_todo(
            user: group_work_item.author,
            author: group_work_item.author,
            target: group_work_item,
            action: Todo::MARKED,
            project: nil,
            group: group
          )
        end
      end
    end

    describe '#todo_exists?' do
      it 'returns false when no todo exist for the given work_item' do
        expect(service.todo_exist?(work_item, author)).to be_falsy
      end

      it 'returns true when a todo exist for the given work_item' do
        service.mark_todo(work_item, author)

        expect(service.todo_exist?(work_item, author)).to be_truthy
      end
    end

    describe '#resolve_todos_for_target' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: work_item, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: work_item, author: author)

        service.resolve_todos_for_target(work_item, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end

      describe 'cached counts' do
        it 'updates when todos change' do
          create(:todo, :assigned, user: john_doe, project: project, target: work_item, author: author)

          expect(john_doe.todos_done_count).to eq(0)
          expect(john_doe.todos_pending_count).to eq(1)
          expect(john_doe).to receive(:update_todos_count_cache).and_call_original

          service.resolve_todos_for_target(work_item, john_doe)

          expect(john_doe.todos_done_count).to eq(1)
          expect(john_doe.todos_pending_count).to eq(0)
        end
      end
    end
  end

  describe '#reassigned_assignable' do
    let(:described_method) { :reassigned_assignable }

    context 'assignable is a merge request' do
      it_behaves_like 'reassigned target' do
        let(:target_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
        let(:addressed_target_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
        let(:target_unassigned) { create(:merge_request, source_project: project, author: author, assignees: []) }
      end
    end

    context 'assignable is a project level issue' do
      it_behaves_like 'reassigned target' do
        let(:target_assigned) { create(:issue, project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
        let(:addressed_target_assigned) { create(:issue, project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
        let(:target_unassigned) { create(:issue, project: project, author: author, assignees: []) }
      end
    end

    context 'assignable is a project level work_item' do
      it_behaves_like 'reassigned target' do
        let(:target_assigned) { create(:work_item, project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
        let(:addressed_target_assigned) { create(:work_item, project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
        let(:target_unassigned) { create(:work_item, project: project, author: author, assignees: []) }
      end
    end

    context 'assignable is a group level issue' do
      it_behaves_like 'reassigned target' do
        let(:additional_todo_attributes) { { project: nil, group: group } }
        let(:target_assigned) { create(:issue, :group_level, namespace: group, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
        let(:addressed_target_assigned) { create(:issue, :group_level, namespace: group, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
        let(:target_unassigned) { create(:issue, :group_level, namespace: group, author: author, assignees: []) }
      end
    end

    context 'assignable is a group level work item' do
      it_behaves_like 'reassigned target' do
        let(:additional_todo_attributes) { { project: nil, group: group } }
        let(:target_assigned) { create(:work_item, :group_level, namespace: group, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
        let(:addressed_target_assigned) { create(:work_item, :group_level, namespace: group, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
        let(:target_unassigned) { create(:work_item, :group_level, namespace: group, author: author, assignees: []) }
      end
    end

    context 'assignable is an alert' do
      it_behaves_like 'reassigned target' do
        let(:target_assigned) { create(:alert_management_alert, project: project, assignees: [john_doe]) }
        let(:addressed_target_assigned) { create(:alert_management_alert, project: project, assignees: [john_doe]) }
        let(:target_unassigned) { create(:alert_management_alert, project: project, assignees: []) }
      end
    end
  end

  describe '#reassigned_reviewable' do
    let(:described_method) { :reassigned_reviewable }

    context 'reviewable is a merge request' do
      it_behaves_like 'reassigned reviewable target' do
        let(:assigned_reviewers) { [] }
        let(:target) { create(:merge_request, source_project: project, author: author, reviewers: assigned_reviewers) }
      end
    end
  end

  describe 'Merge Requests' do
    let(:mentioned_mr) { create(:merge_request, source_project: project, author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
    let(:addressed_mr) { create(:merge_request, source_project: project, author: author, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
    let(:assigned_mr) { create(:merge_request, source_project: project, author: author, assignees: [john_doe]) }
    let(:unassigned_mr) { create(:merge_request, source_project: project, author: author, assignees: []) }

    describe '#new_merge_request' do
      it 'creates a pending todo if assigned' do
        service.new_merge_request(assigned_mr, author)

        should_create_todo(user: john_doe, target: assigned_mr, action: Todo::ASSIGNED)
      end

      it 'does not create a todo if unassigned' do
        should_not_create_any_todo { service.new_merge_request(unassigned_mr, author) }
      end

      it 'creates a todo if assignee is the current user' do
        service.new_merge_request(assigned_mr, john_doe)

        should_create_todo(user: john_doe, target: assigned_mr, author: john_doe, action: Todo::ASSIGNED)
      end

      it 'creates a todo for each valid mentioned user' do
        service.new_merge_request(mentioned_mr, author)

        should_create_todo(user: member, target: mentioned_mr, action: Todo::MENTIONED)
        should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED)
        should_create_todo(user: author, target: mentioned_mr, action: Todo::MENTIONED)
        should_create_todo(user: john_doe, target: mentioned_mr, action: Todo::MENTIONED)
        should_not_create_todo(user: non_member, target: mentioned_mr, action: Todo::MENTIONED)
      end

      it 'creates a todo for each valid user based on the type of mention' do
        mentioned_mr.update!(description: directly_addressed_and_mentioned)

        service.new_merge_request(mentioned_mr, author)

        should_create_todo(user: member, target: mentioned_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: admin, target: mentioned_mr, action: Todo::MENTIONED)
      end

      it 'creates a directly addressed todo for each valid addressed user' do
        service.new_merge_request(addressed_mr, author)

        should_create_todo(user: member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: guest, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: author, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: non_member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
      end
    end

    describe '#update_merge_request' do
      it 'creates a todo for each valid mentioned user not included in skip_users' do
        service.update_merge_request(mentioned_mr, author, skip_users)

        should_create_todo(user: member, target: mentioned_mr, action: Todo::MENTIONED)
        should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED)
        should_create_todo(user: john_doe, target: mentioned_mr, action: Todo::MENTIONED)
        should_create_todo(user: author, target: mentioned_mr, action: Todo::MENTIONED)
        should_not_create_todo(user: non_member, target: mentioned_mr, action: Todo::MENTIONED)
        should_not_create_todo(user: skipped, target: mentioned_mr, action: Todo::MENTIONED)
      end

      it 'creates a todo for each valid user not included in skip_users based on the type of mention' do
        mentioned_mr.update!(description: directly_addressed_and_mentioned)

        service.update_merge_request(mentioned_mr, author, skip_users)

        should_create_todo(user: member, target: mentioned_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: admin, target: mentioned_mr, action: Todo::MENTIONED)
        should_not_create_todo(user: skipped, target: mentioned_mr)
      end

      it 'creates a directly addressed todo for each valid addressed user not included in skip_users' do
        service.update_merge_request(addressed_mr, author, skip_users)

        should_create_todo(user: member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: guest, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: john_doe, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_create_todo(user: author, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: non_member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        should_not_create_todo(user: skipped, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
      end

      it 'does not create a todo if user was already mentioned and todo is pending' do
        stub_feature_flags(multiple_todos: false)

        create(:todo, :mentioned, user: member, project: project, target: mentioned_mr, author: author)

        expect { service.update_merge_request(mentioned_mr, author) }.not_to change(member.todos, :count)
      end

      it 'does not create a todo if user was already mentioned and todo is done' do
        create(:todo, :mentioned, :done, user: skipped, project: project, target: mentioned_mr, author: author)

        expect { service.update_merge_request(mentioned_mr, author, skip_users) }.not_to change(skipped.todos, :count)
      end

      it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do
        stub_feature_flags(multiple_todos: false)

        create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr, author: author)

        expect { service.update_merge_request(addressed_mr, author) }.not_to change(member.todos, :count)
      end

      it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do
        create(:todo, :directly_addressed, user: skipped, project: project, target: addressed_mr, author: author)

        expect { service.update_merge_request(addressed_mr, author, skip_users) }.not_to change(skipped.todos, :count)
      end

      context 'with a task list' do
        it 'does not create todo when tasks are marked as completed' do
          mentioned_mr.update!(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")

          service.update_merge_request(mentioned_mr, author)

          should_not_create_todo(user: admin, target: mentioned_mr, action: Todo::MENTIONED)
          should_not_create_todo(user: assignee, target: mentioned_mr, action: Todo::MENTIONED)
          should_not_create_todo(user: author, target: mentioned_mr, action: Todo::MENTIONED)
          should_not_create_todo(user: john_doe, target: mentioned_mr, action: Todo::MENTIONED)
          should_not_create_todo(user: member, target: mentioned_mr, action: Todo::MENTIONED)
          should_not_create_todo(user: non_member, target: mentioned_mr, action: Todo::MENTIONED)
          should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED)
        end

        it 'does not create directly addressed todo when tasks are marked as completed' do
          addressed_mr.update!(description: "#{directly_addressed}\n- [x] Task 1\n- [X] Task 2")

          service.update_merge_request(addressed_mr, author)

          should_not_create_todo(user: admin, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: assignee, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: author, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: john_doe, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: non_member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
          should_not_create_todo(user: guest, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
        end

        it 'does not raise an error when description not change' do
          mentioned_mr.update!(title: 'Sample')

          expect { service.update_merge_request(mentioned_mr, author) }.not_to raise_error
        end
      end
    end

    describe '#close_merge_request' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mentioned_mr, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mentioned_mr, author: author)
        service.close_merge_request(mentioned_mr, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end
    end

    describe '#merge_merge_request' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mentioned_mr, author: author)
        second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mentioned_mr, author: author)
        service.merge_merge_request(mentioned_mr, john_doe)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).to be_done
      end

      it 'does not create todo for guests' do
        service.merge_merge_request(mentioned_mr, john_doe)
        should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED)
      end

      it 'does not create directly addressed todo for guests' do
        service.merge_merge_request(addressed_mr, john_doe)
        should_not_create_todo(user: guest, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED)
      end
    end

    describe '#new_award_emoji' do
      it 'marks related pending todos to the target for the user as done' do
        todo = create(:todo, user: john_doe, project: project, target: mentioned_mr, author: author)
        service.new_award_emoji(mentioned_mr, john_doe)

        expect(todo.reload).to be_done
      end
    end

    describe '#merge_request_build_failed' do
      let(:merge_participants) { [unassigned_mr.author, admin] }

      before do
        allow(unassigned_mr).to receive(:merge_participants).and_return(merge_participants)
      end

      it 'creates a pending todo for each merge_participant' do
        service.merge_request_build_failed(unassigned_mr)

        merge_participants.each do |participant|
          should_create_todo(user: participant, author: participant, target: unassigned_mr, action: Todo::BUILD_FAILED)
        end
      end
    end

    describe '#merge_request_push' do
      it 'marks related pending todos to the target for the user as done' do
        first_todo = create(:todo, :build_failed, user: author, project: project, target: mentioned_mr, author: john_doe)
        second_todo = create(:todo, :build_failed, user: john_doe, project: project, target: mentioned_mr, author: john_doe)
        service.merge_request_push(mentioned_mr, author)

        expect(first_todo.reload).to be_done
        expect(second_todo.reload).not_to be_done
      end
    end

    describe '#merge_request_became_unmergeable' do
      let(:merge_participants) { [admin, create(:user)] }

      before do
        allow(unassigned_mr).to receive(:merge_participants).and_return(merge_participants)
      end

      it 'creates a pending todo for each merge_participant' do
        unassigned_mr.update!(merge_when_pipeline_succeeds: true, merge_user: admin)
        service.merge_request_became_unmergeable(unassigned_mr)

        merge_participants.each do |participant|
          should_create_todo(user: participant, author: participant, target: unassigned_mr, action: Todo::UNMERGEABLE)
        end
      end
    end

    describe '#mark_todo' do
      it 'creates a todo from a merge request' do
        service.mark_todo(unassigned_mr, author)

        should_create_todo(user: author, target: unassigned_mr, action: Todo::MARKED)
      end
    end

    describe '#new_note' do
      let_it_be(:project) { create(:project, :repository) }

      before_all do
        project.add_guest(guest)
        project.add_developer(author)
        project.add_developer(assignee)
        project.add_developer(member)
        project.add_developer(john_doe)
        project.add_developer(skipped)
      end

      let(:mention) { john_doe.to_reference }
      let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: unassigned_mr, author: author, note: "Hey #{mention}") }
      let(:addressed_diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: unassigned_mr, author: author, note: "#{mention}, hey!") }
      let(:legacy_diff_note_on_merge_request) { create(:legacy_diff_note_on_merge_request, project: project, noteable: unassigned_mr, author: author, note: "Hey #{mention}") }

      it 'creates a todo for mentioned user on new diff note' do
        service.new_note(diff_note_on_merge_request, author)

        should_create_todo(user: john_doe, target: unassigned_mr, author: author, action: Todo::MENTIONED, note: diff_note_on_merge_request)
      end

      it 'creates a directly addressed todo for addressed user on new diff note' do
        service.new_note(addressed_diff_note_on_merge_request, author)

        should_create_todo(user: john_doe, target: unassigned_mr, author: author, action: Todo::DIRECTLY_ADDRESSED, note: addressed_diff_note_on_merge_request)
      end

      it 'creates a todo for mentioned user on legacy diff note' do
        service.new_note(legacy_diff_note_on_merge_request, author)

        should_create_todo(user: john_doe, target: unassigned_mr, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request)
      end

      it 'does not create todo for guests' do
        note_on_merge_request = create :note_on_merge_request, project: project, noteable: mentioned_mr, note: mentions
        service.new_note(note_on_merge_request, author)

        should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED)
      end
    end
  end

  describe 'Designs' do
    include DesignManagementTestHelpers

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

    before do
      enable_design_management

      project.add_guest(author)
      project.add_developer(john_doe)
    end

    let(:note) do
      build(
        :diff_note_on_design,
        noteable: design,
        author: author,
        note: "Hey #{john_doe.to_reference}"
      )
    end

    it 'creates a todo for mentioned user on new diff note' do
      service.new_note(note, author)

      should_create_todo(
        user: john_doe,
        target: design,
        action: Todo::MENTIONED,
        note: note
      )
    end
  end

  describe '#update_note' do
    let_it_be(:noteable) { create(:issue, project: project) }

    let(:note) { create(:note, project: project, note: mentions, noteable: noteable) }
    let(:addressed_note) { create(:note, project: project, note: directly_addressed.to_s, noteable: noteable) }

    it 'creates a todo for each valid mentioned user not included in skip_users' do
      service.update_note(note, author, skip_users)

      should_create_todo(user: member, target: noteable, action: Todo::MENTIONED)
      should_create_todo(user: guest, target: noteable, action: Todo::MENTIONED)
      should_create_todo(user: john_doe, target: noteable, action: Todo::MENTIONED)
      should_create_todo(user: author, target: noteable, action: Todo::MENTIONED)
      should_not_create_todo(user: non_member, target: noteable, action: Todo::MENTIONED)
      should_not_create_todo(user: skipped, target: noteable, action: Todo::MENTIONED)
    end

    it 'creates a todo for each valid user not included in skip_users based on the type of mention' do
      note.update!(note: directly_addressed_and_mentioned)

      service.update_note(note, author, skip_users)

      should_create_todo(user: member, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_create_todo(user: guest, target: noteable, action: Todo::MENTIONED)
      should_not_create_todo(user: admin, target: noteable, action: Todo::MENTIONED)
      should_not_create_todo(user: skipped, target: noteable)
    end

    it 'creates a directly addressed todo for each valid addressed user not included in skip_users' do
      service.update_note(addressed_note, author, skip_users)

      should_create_todo(user: member, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_create_todo(user: guest, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_create_todo(user: john_doe, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_create_todo(user: author, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_not_create_todo(user: non_member, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
      should_not_create_todo(user: skipped, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
    end

    context 'users already have pending todos and the multiple_todos feature is off' do
      before do
        stub_feature_flags(multiple_todos: false)
      end

      let_it_be(:pending_todo_for_member) { create(:todo, :mentioned, user: member, project: project, target: noteable) }
      let_it_be(:pending_todo_for_guest) { create(:todo, :mentioned, user: guest, project: project, target: noteable) }
      let_it_be(:pending_todo_for_admin) { create(:todo, :mentioned, user: admin, project: project, target: noteable) }
      let_it_be(:note_mentioning_1_user) do
        create(:note, project: project, note: "FYI #{member.to_reference}", noteable: noteable)
      end

      let_it_be(:note_mentioning_3_users) do
        create(:note, project: project, note: 'FYI: ' + [member, guest, admin].map(&:to_reference).join(' '), noteable: noteable)
      end

      it 'does not create a todo if user was already mentioned and todo is pending' do
        expect { service.update_note(note_mentioning_1_user, author, skip_users) }.not_to change(member.todos, :count)
      end

      it 'does not create N+1 queries for pending todos' do
        # Excluding queries for user permissions because those do execute N+1 queries
        allow_any_instance_of(User).to receive(:can?).and_return(true)

        control_count = ActiveRecord::QueryRecorder.new { service.update_note(note_mentioning_1_user, author, skip_users) }.count

        expect { service.update_note(note_mentioning_3_users, author, skip_users) }.not_to exceed_query_limit(control_count)
      end
    end

    it 'does not create a todo if user was already mentioned and todo is done' do
      create(:todo, :mentioned, :done, user: skipped, project: project, target: noteable, author: author)

      expect { service.update_note(note, author, skip_users) }.not_to change(skipped.todos, :count)
    end

    it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do
      stub_feature_flags(multiple_todos: false)

      create(:todo, :directly_addressed, user: member, project: project, target: noteable, author: author)

      expect { service.update_note(addressed_note, author, skip_users) }.not_to change(member.todos, :count)
    end

    it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do
      create(:todo, :directly_addressed, :done, user: skipped, project: project, target: noteable, author: author)

      expect { service.update_note(addressed_note, author, skip_users) }.not_to change(skipped.todos, :count)
    end
  end

  it 'updates cached counts when a todo is created' do
    issue = create(:issue, project: project, assignees: [john_doe], author: author)

    expect_next(Users::UpdateTodoCountCacheService, [john_doe.id]).to receive(:execute)

    service.new_issue(issue, author)
  end

  shared_examples 'updating todos state' do |state, new_state, new_resolved_by = nil|
    let!(:first_todo) { create(:todo, state, user: john_doe) }
    let!(:second_todo) { create(:todo, state, user: john_doe) }
    let(:collection) { Todo.all }

    it 'updates related todos for the user with the new_state' do
      method_call

      expect(collection.all? { |todo| todo.reload.state?(new_state) }).to be_truthy
    end

    if new_resolved_by
      it 'updates resolution mechanism' do
        method_call

        expect(collection.all? { |todo| todo.reload.resolved_by_action == new_resolved_by }).to be_truthy
      end
    end

    it 'returns the updated ids' do
      expect(method_call).to match_array([first_todo.id, second_todo.id])
    end

    describe 'cached counts' do
      it 'updates when todos change' do
        expect(john_doe.todos.where(state: new_state).count).to eq(0)
        expect(john_doe.todos.where(state: state).count).to eq(2)
        expect(john_doe).to receive(:update_todos_count_cache).and_call_original

        method_call

        expect(john_doe.todos.where(state: new_state).count).to eq(2)
        expect(john_doe.todos.where(state: state).count).to eq(0)
      end
    end
  end

  describe '#resolve_todos' do
    it_behaves_like 'updating todos state', :pending, :done, 'mark_done' do
      subject(:method_call) do
        service.resolve_todos(collection, john_doe, resolution: :done, resolved_by_action: :mark_done)
      end
    end
  end

  describe '#restore_todos' do
    it_behaves_like 'updating todos state', :done, :pending do
      subject(:method_call) do
        service.restore_todos(collection, john_doe)
      end
    end
  end

  describe '#resolve_todo' do
    let!(:todo) { create(:todo, :assigned, user: john_doe) }

    it 'marks pending todo as done' do
      expect do
        service.resolve_todo(todo, john_doe)
        todo.reload
      end.to change { todo.done? }.to(true)
    end

    it 'saves resolution mechanism' do
      expect do
        service.resolve_todo(todo, john_doe, resolved_by_action: :mark_done)
        todo.reload
      end.to change { todo.resolved_by_mark_done? }.to(true)
    end

    context 'cached counts' do
      it 'updates when todos change' do
        expect(john_doe.todos_done_count).to eq(0)
        expect(john_doe.todos_pending_count).to eq(1)
        expect(john_doe).to receive(:update_todos_count_cache).and_call_original

        service.resolve_todo(todo, john_doe)

        expect(john_doe.todos_done_count).to eq(1)
        expect(john_doe.todos_pending_count).to eq(0)
      end
    end
  end

  describe '#resolve_access_request_todos' do
    let_it_be(:group) { create(:group, :public) }
    let_it_be(:group_requester) { create(:group_member, :access_request, group: group, user: assignee) }
    let_it_be(:project_requester) { create(:project_member, :access_request, project: project, user: non_member) }
    let_it_be(:another_pending_todo) { create(:todo, state: :pending, user: john_doe) }
    # access request by another user
    let_it_be(:another_group_todo) do
      create(:todo, state: :pending, target: group, action: Todo::MEMBER_ACCESS_REQUESTED)
    end

    let_it_be(:another_project_todo) do
      create(:todo, state: :pending, target: project, action: Todo::MEMBER_ACCESS_REQUESTED)
    end

    it 'marks the todos for group access request handlers as done' do
      access_request_todos = [member, john_doe].map do |group_user|
        create(:todo,
          user: group_user,
          state: :pending,
          action: Todo::MEMBER_ACCESS_REQUESTED,
          author: group_requester.user,
          target: group
        )
      end

      expect do
        service.resolve_access_request_todos(group_requester)
      end.to change {
        Todo.pending.where(target: group).for_author(group_requester.user)
          .for_action(Todo::MEMBER_ACCESS_REQUESTED).count
      }.from(2).to(0)

      expect(access_request_todos.each(&:reload)).to all be_done
      expect(another_pending_todo.reload).not_to be_done
      expect(another_group_todo.reload).not_to be_done
    end

    it 'marks the todos for project access request handlers as done' do
      # The project has 1 owner already. Adding another owner here
      project.add_member(john_doe, Gitlab::Access::OWNER)

      access_request_todo = create(:todo,
        user: john_doe,
        state: :pending,
        action: Todo::MEMBER_ACCESS_REQUESTED,
        author: project_requester.user,
        target: project
      )

      expect do
        service.resolve_access_request_todos(project_requester)
      end.to change {
        Todo.pending.where(target: project).for_author(project_requester.user)
          .for_action(Todo::MEMBER_ACCESS_REQUESTED).count
      }.from(2).to(0) # The original owner todo was created with the pending access request

      expect(access_request_todo.reload).to be_done
      expect(another_pending_todo.reload).to be_pending
      expect(another_project_todo.reload).to be_pending
    end
  end

  describe '#restore_todo' do
    let!(:todo) { create(:todo, :done, user: john_doe) }

    it 'marks resolved todo as pending' do
      expect do
        service.restore_todo(todo, john_doe)
        todo.reload
      end.to change { todo.pending? }.to(true)
    end

    context 'cached counts' do
      it 'updates when todos change' do
        expect(john_doe.todos_done_count).to eq(1)
        expect(john_doe.todos_pending_count).to eq(0)
        expect(john_doe).to receive(:update_todos_count_cache).and_call_original

        service.restore_todo(todo, john_doe)

        expect(john_doe.todos_done_count).to eq(0)
        expect(john_doe.todos_pending_count).to eq(1)
      end
    end
  end

  describe '#create_request_review_todo' do
    let(:target) { create(:merge_request, author: author, source_project: project) }
    let(:reviewer) { create(:user) }

    it 'creates a todo for reviewer' do
      service.create_request_review_todo(target, author, reviewer)

      should_create_todo(user: reviewer, target: target, action: Todo::REVIEW_REQUESTED)
    end
  end

  describe '#create_member_access_request_todos' do
    let_it_be(:group) { create(:group, :public) }
    let_it_be(:project) { create(:project, :public, group: group) }

    shared_examples 'member access request is raised' do
      context 'when the source has more than 10 owners' do
        it 'creates todos for 10 recently active source owners' do
          users = create_list(:user, 12, :with_sign_ins)
          users.each do |user|
            source.add_owner(user)
          end
          ten_most_recently_active_source_owners = users.sort_by(&:last_sign_in_at).last(10)
          excluded_source_owners = users - ten_most_recently_active_source_owners

          service.create_member_access_request_todos(requester1)

          ten_most_recently_active_source_owners.each do |owner|
            expect(Todo.where(user: owner, target: source, action: Todo::MEMBER_ACCESS_REQUESTED, author: requester1.user).count).to eq 1
          end

          excluded_source_owners.each do |owner|
            expect(Todo.where(user: owner, target: source, action: Todo::MEMBER_ACCESS_REQUESTED, author: requester1.user).count).to eq 0
          end
        end
      end

      context 'when total owners are less than 10' do
        it 'creates todos for all source owners' do
          users = create_list(:user, 4, :with_sign_ins)
          users.map do |user|
            source.add_owner(user)
          end

          service.create_member_access_request_todos(requester1)

          users.each do |owner|
            expect(Todo.where(user: owner, target: source, action: Todo::MEMBER_ACCESS_REQUESTED, author: requester1.user).count).to eq 1
          end
        end
      end

      context 'when multiple access requests are raised' do
        it 'creates todos for 10 recently active source owners for multiple requests' do
          users = create_list(:user, 12, :with_sign_ins)
          users.each do |user|
            source.add_owner(user)
          end
          ten_most_recently_active_source_owners = users.sort_by(&:last_sign_in_at).last(10)
          excluded_source_owners = users - ten_most_recently_active_source_owners

          service.create_member_access_request_todos(requester1)
          service.create_member_access_request_todos(requester2)

          ten_most_recently_active_source_owners.each do |owner|
            expect(Todo.where(user: owner, target: source, action: Todo::MEMBER_ACCESS_REQUESTED, author: requester1.user).count).to eq 1
            expect(Todo.where(user: owner, target: source, action: Todo::MEMBER_ACCESS_REQUESTED, author: requester2.user).count).to eq 1
          end

          excluded_source_owners.each do |owner|
            expect(Todo.where(user: owner, target: source, action: Todo::MEMBER_ACCESS_REQUESTED, author: requester1.user).count).to eq 0
            expect(Todo.where(user: owner, target: source, action: Todo::MEMBER_ACCESS_REQUESTED, author: requester2.user).count).to eq 0
          end
        end
      end
    end

    context 'when request is raised for group' do
      it_behaves_like 'member access request is raised' do
        let_it_be(:source) { create(:group, :public) }
        let_it_be(:requester1) { create(:group_member, :access_request, group: source, user: assignee) }
        let_it_be(:requester2) { create(:group_member, :access_request, group: source, user: non_member) }
      end
    end

    context 'when request is raised for project' do
      it_behaves_like 'member access request is raised' do
        let_it_be(:source) { create(:project, :public) }
        let_it_be(:requester1) { create(:project_member, :access_request, project: source, user: assignee) }
        let_it_be(:requester2) { create(:project_member, :access_request, project: source, user: non_member) }
      end
    end
  end

  def should_create_todo(attributes = {})
    attributes.reverse_merge!(
      project: project,
      author: author,
      state: :pending
    )

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

  def should_not_create_todo(attributes = {})
    attributes.reverse_merge!(
      project: project,
      author: author,
      state: :pending
    )

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

  def should_not_create_any_todo
    expect { yield }.not_to change(Todo, :count)
  end
end


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

# Base class for services that count a single resource such as the number of
# issues for a project.
class BaseCountService
  def relation_for_count
    raise(
      NotImplementedError,
      '"relation_for_count" must be implemented and return an ActiveRecord::Relation'
    )
  end

  def count
    Rails.cache.fetch(cache_key, cache_options) { uncached_count }.to_i
  end

  def count_stored?
    Rails.cache.read(cache_key).present?
  end

  def refresh_cache(&block)
    update_cache_for_key(cache_key, &block)
  end

  def uncached_count
    relation_for_count.count
  end

  def delete_cache
    ::Gitlab::Cache.delete(cache_key)
  end

  def raw?
    false
  end

  def cache_key
    raise NotImplementedError, 'cache_key must be implemented and return a String, Array, or Hash'
  end

  # subclasses can override to add any specific options, such as
  #   super.merge({ expires_in: 5.minutes })
  def cache_options
    { raw: raw? }
  end

  def update_cache_for_key(key, &block)
    Rails.cache.write(key, block ? yield : uncached_count, raw: raw?)
  end
end

BaseCountService.prepend_mod

```

Response:
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe BaseCountService, :use_clean_rails_memory_store_caching, feature_category: :shared do
  let(:service) { described_class.new }

  describe '#relation_for_count' do
    it 'raises NotImplementedError' do
      expect { service.relation_for_count }.to raise_error(NotImplementedError)
    end
  end

  describe '#count' do
    it 'returns the number of values' do
      expect(service)
        .to receive(:cache_key)
        .and_return('foo')

      expect(service)
        .to receive(:uncached_count)
        .and_return(5)

      expect(service.count).to eq(5)
    end
  end

  describe '#uncached_count' do
    it 'returns the uncached number of values' do
      expect(service)
        .to receive(:relation_for_count)
        .and_return(double(:relation, count: 5))

      expect(service.uncached_count).to eq(5)
    end
  end

  describe '#refresh_cache' do
    it 'refreshes the cache' do
      allow(service)
        .to receive(:cache_key)
        .and_return('foo')

      allow(service)
        .to receive(:uncached_count)
        .and_return(4)

      service.refresh_cache

      expect(Rails.cache.fetch(service.cache_key, raw: service.raw?)).to eq(4)
    end
  end

  describe '#delete_cache' do
    it 'deletes the cache' do
      allow(service)
        .to receive(:cache_key)
        .and_return('foo')

      allow(service)
        .to receive(:uncached_count)
        .and_return(4)

      service.refresh_cache
      service.delete_cache

      expect(Rails.cache.fetch(service.cache_key, raw: service.raw?)).to be_nil
    end
  end

  describe '#raw?' do
    it 'returns false' do
      expect(service.raw?).to eq(false)
    end
  end

  describe '#cache_key' do
    it 'raises NotImplementedError' do
      expect { service.cache_key }.to raise_error(NotImplementedError)
    end
  end

  describe '#cache_options' do
    it 'returns the default in options' do
      expect(service.cache_options).to eq({ raw: false })
    end
  end
end