Refactor AutocompleteController
This refactors the AutocompleteController according to the guidelines and boundaries discussed in https://gitlab.com/gitlab-org/gitlab-ce/issues/49653. Specifically, ActiveRecord logic is moved to different finders, which are then used in the controller. View logic in turn is moved to presenters, instead of directly using ActiveRecord's "to_json" method. The finder MoveToProjectFinder is also adjusted according to the abstraction guidelines and boundaries, resulting in a much more simple finder. By using finders (and other abstractions) more actively, we can push a lot of logic out of the controller. We also remove the need for various "before_action" hooks, though this could be achieved without using finders as well. The various finders related to AutcompleteController have also been moved into a namespace. This removes the need for calling everything "AutocompleteSmurfFinder", instead you can use "Autocomplete::SmurfFinder".
This commit is contained in:
parent
0a73c1c583
commit
6f3c490107
|
|
@ -1,67 +1,39 @@
|
|||
class AutocompleteController < ApplicationController
|
||||
AWARD_EMOJI_MAX = 100
|
||||
|
||||
skip_before_action :authenticate_user!, only: [:users, :award_emojis]
|
||||
before_action :load_project, only: [:users]
|
||||
before_action :load_group, only: [:users]
|
||||
|
||||
def users
|
||||
@users = AutocompleteUsersFinder.new(params: params, current_user: current_user, project: @project, group: @group).execute
|
||||
project = Autocomplete::ProjectFinder
|
||||
.new(current_user, params)
|
||||
.execute
|
||||
|
||||
render json: UserSerializer.new.represent(@users)
|
||||
group = Autocomplete::GroupFinder
|
||||
.new(current_user, project, params)
|
||||
.execute
|
||||
|
||||
users = Autocomplete::UsersFinder
|
||||
.new(params: params, current_user: current_user, project: project, group: group)
|
||||
.execute
|
||||
|
||||
render json: UserSerializer.new.represent(users)
|
||||
end
|
||||
|
||||
def user
|
||||
@user = User.find(params[:id])
|
||||
render json: UserSerializer.new.represent(@user)
|
||||
user = UserFinder.new(params).execute!
|
||||
|
||||
render json: UserSerializer.new.represent(user)
|
||||
end
|
||||
|
||||
# Displays projects to use for the dropdown when moving a resource from one
|
||||
# project to another.
|
||||
def projects
|
||||
project = Project.find_by_id(params[:project_id])
|
||||
projects = projects_finder.execute(project, search: params[:search], offset_id: params[:offset_id])
|
||||
projects = Autocomplete::MoveToProjectFinder
|
||||
.new(current_user, params)
|
||||
.execute
|
||||
|
||||
render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace)
|
||||
render json: MoveToProjectSerializer.new.represent(projects)
|
||||
end
|
||||
|
||||
def award_emojis
|
||||
emoji_with_count = AwardEmoji
|
||||
.limit(AWARD_EMOJI_MAX)
|
||||
.where(user: current_user)
|
||||
.group(:name)
|
||||
.order('count_all DESC, name ASC')
|
||||
.count
|
||||
|
||||
# Transform from hash to array to guarantee json order
|
||||
# e.g. { 'thumbsup' => 2, 'thumbsdown' = 1 }
|
||||
# => [{ name: 'thumbsup' }, { name: 'thumbsdown' }]
|
||||
render json: emoji_with_count.map { |k, v| { name: k } }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_group
|
||||
@group ||= begin
|
||||
if @project.blank? && params[:group_id].present?
|
||||
group = Group.find(params[:group_id])
|
||||
return render_404 unless can?(current_user, :read_group, group)
|
||||
|
||||
group
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_project
|
||||
@project ||= begin
|
||||
if params[:project_id].present?
|
||||
project = Project.find(params[:project_id])
|
||||
return render_404 unless can?(current_user, :read_project, project)
|
||||
|
||||
project
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def projects_finder
|
||||
MoveToProjectFinder.new(current_user)
|
||||
render json: AwardedEmojiFinder.new(current_user).execute
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Autocomplete
|
||||
# Finder for retrieving a group to use for autocomplete data sources.
|
||||
class GroupFinder
|
||||
attr_reader :current_user, :project, :group_id
|
||||
|
||||
# current_user - The currently logged in user, if any.
|
||||
# project - The Project (if any) to use for the autocomplete data sources.
|
||||
# params - A Hash containing parameters to use for finding the project.
|
||||
#
|
||||
# The following parameters are supported:
|
||||
#
|
||||
# * group_id: The ID of the group to find.
|
||||
def initialize(current_user = nil, project = nil, params = {})
|
||||
@current_user = current_user
|
||||
@project = project
|
||||
@group_id = params[:group_id]
|
||||
end
|
||||
|
||||
# Attempts to find a Group based on the current group ID.
|
||||
def execute
|
||||
return unless project.blank? && group_id.present?
|
||||
|
||||
group = Group.find(group_id)
|
||||
|
||||
# This removes the need for using `return render_404` and similar patterns
|
||||
# in controllers that use this finder.
|
||||
unless Ability.allowed?(current_user, :read_group, group)
|
||||
raise ActiveRecord::RecordNotFound
|
||||
.new("Could not find a Group with ID #{group_id}")
|
||||
end
|
||||
|
||||
group
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Autocomplete
|
||||
# Finder that retrieves a list of projects that an issue can be moved to.
|
||||
class MoveToProjectFinder
|
||||
attr_reader :current_user, :search, :project_id, :offset_id
|
||||
|
||||
# current_user - The User object of the user that wants to view the list of
|
||||
# projects.
|
||||
#
|
||||
# params - A Hash containing additional parameters to set.
|
||||
#
|
||||
# The following parameters can be set (as Symbols):
|
||||
#
|
||||
# * search: An optional search query to apply to the list of projects.
|
||||
# * project_id: The ID of a project to exclude from the returned relation.
|
||||
# * offset_id: The ID of a project to use for pagination. When given, only
|
||||
# projects with a lower ID are included in the list.
|
||||
def initialize(current_user, params = {})
|
||||
@current_user = current_user
|
||||
@search = params[:search]
|
||||
@project_id = params[:project_id]
|
||||
@offset_id = params[:offset_id]
|
||||
end
|
||||
|
||||
def execute
|
||||
current_user
|
||||
.projects_where_can_admin_issues
|
||||
.optionally_search(search)
|
||||
.excluding_project(project_id)
|
||||
.paginate_in_descending_order_using_id(before: offset_id)
|
||||
.eager_load_namespace_and_owner
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Autocomplete
|
||||
# Finder for retrieving a project to use for autocomplete data sources.
|
||||
class ProjectFinder
|
||||
attr_reader :current_user, :project_id
|
||||
|
||||
# current_user - The currently logged in user, if any.
|
||||
# params - A Hash containing parameters to use for finding the project.
|
||||
#
|
||||
# The following parameters are supported:
|
||||
#
|
||||
# * project_id: The ID of the project to find.
|
||||
def initialize(current_user = nil, params = {})
|
||||
@current_user = current_user
|
||||
@project_id = params[:project_id]
|
||||
end
|
||||
|
||||
# Attempts to find a Project based on the current project ID.
|
||||
def execute
|
||||
return if project_id.blank?
|
||||
|
||||
project = Project.find(project_id)
|
||||
|
||||
# This removes the need for using `return render_404` and similar patterns
|
||||
# in controllers that use this finder.
|
||||
unless Ability.allowed?(current_user, :read_project, project)
|
||||
raise ActiveRecord::RecordNotFound
|
||||
.new("Could not find a Project with ID #{project_id}")
|
||||
end
|
||||
|
||||
project
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Autocomplete
|
||||
class UsersFinder
|
||||
# The number of users to display in the results is hardcoded to 20, and
|
||||
# pagination is not supported. This ensures that performance remains
|
||||
# consistent and removes the need for implementing keyset pagination to
|
||||
# ensure good performance.
|
||||
LIMIT = 20
|
||||
|
||||
attr_reader :current_user, :project, :group, :search, :skip_users,
|
||||
:author_id, :todo_filter, :todo_state_filter,
|
||||
:filter_by_current_user
|
||||
|
||||
def initialize(params:, current_user:, project:, group:)
|
||||
@current_user = current_user
|
||||
@project = project
|
||||
@group = group
|
||||
@search = params[:search]
|
||||
@skip_users = params[:skip_users]
|
||||
@author_id = params[:author_id]
|
||||
@todo_filter = params[:todo_filter]
|
||||
@todo_state_filter = params[:todo_state_filter]
|
||||
@filter_by_current_user = params[:current_user]
|
||||
end
|
||||
|
||||
def execute
|
||||
items = limited_users
|
||||
|
||||
if search.blank?
|
||||
# Include current user if available to filter by "Me"
|
||||
items.unshift(current_user) if prepend_current_user?
|
||||
|
||||
if prepend_author? && (author = User.find_by_id(author_id))
|
||||
items.unshift(author)
|
||||
end
|
||||
end
|
||||
|
||||
items.uniq
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns the users based on the input parameters, as an Array.
|
||||
#
|
||||
# This method is separate so it is easier to extend in EE.
|
||||
def limited_users
|
||||
# When changing the order of these method calls, make sure that
|
||||
# reorder_by_name() is called _before_ optionally_search(), otherwise
|
||||
# reorder_by_name will break the ORDER BY applied in optionally_search().
|
||||
find_users
|
||||
.active
|
||||
.reorder_by_name
|
||||
.optionally_search(search)
|
||||
.where_not_in(skip_users)
|
||||
.limit_to_todo_authors(
|
||||
user: current_user,
|
||||
with_todos: todo_filter,
|
||||
todo_state: todo_state_filter
|
||||
)
|
||||
.limit(LIMIT)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def prepend_current_user?
|
||||
filter_by_current_user.present? && current_user
|
||||
end
|
||||
|
||||
def prepend_author?
|
||||
author_id.present? && current_user
|
||||
end
|
||||
|
||||
def find_users
|
||||
if project
|
||||
project.authorized_users.union_with_user(author_id)
|
||||
elsif group
|
||||
group.users_with_parents
|
||||
elsif current_user
|
||||
User.all
|
||||
else
|
||||
User.none
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
class AutocompleteUsersFinder
|
||||
# The number of users to display in the results is hardcoded to 20, and
|
||||
# pagination is not supported. This ensures that performance remains
|
||||
# consistent and removes the need for implementing keyset pagination to ensure
|
||||
# good performance.
|
||||
LIMIT = 20
|
||||
|
||||
attr_reader :current_user, :project, :group, :search, :skip_users,
|
||||
:author_id, :params
|
||||
|
||||
def initialize(params:, current_user:, project:, group:)
|
||||
@current_user = current_user
|
||||
@project = project
|
||||
@group = group
|
||||
@search = params[:search]
|
||||
@skip_users = params[:skip_users]
|
||||
@author_id = params[:author_id]
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
items = find_users
|
||||
items = items.active
|
||||
items = items.reorder(:name)
|
||||
items = items.search(search) if search.present?
|
||||
items = items.where.not(id: skip_users) if skip_users.present?
|
||||
items = items.limit(LIMIT)
|
||||
|
||||
if params[:todo_filter].present? && current_user
|
||||
items = items.todo_authors(current_user.id, params[:todo_state_filter])
|
||||
end
|
||||
|
||||
if search.blank?
|
||||
# Include current user if available to filter by "Me"
|
||||
if params[:current_user].present? && current_user
|
||||
items = [current_user, *items].uniq
|
||||
end
|
||||
|
||||
if author_id.present? && current_user
|
||||
author = User.find_by_id(author_id)
|
||||
items = [author, *items].uniq if author
|
||||
end
|
||||
end
|
||||
|
||||
items
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_users
|
||||
return users_from_project if project
|
||||
return group.users_with_parents if group
|
||||
return User.all if current_user
|
||||
|
||||
User.none
|
||||
end
|
||||
|
||||
def users_from_project
|
||||
if author_id.present?
|
||||
union = Gitlab::SQL::Union
|
||||
.new([project.authorized_users, User.where(id: author_id)])
|
||||
|
||||
User.from("(#{union.to_sql}) #{User.table_name}")
|
||||
else
|
||||
project.authorized_users
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Class for retrieving information about emoji awarded _by_ a particular user.
|
||||
class AwardedEmojiFinder
|
||||
attr_reader :current_user
|
||||
|
||||
# current_user - The User to generate the data for.
|
||||
def initialize(current_user = nil)
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def execute
|
||||
return [] unless current_user
|
||||
|
||||
# We want the resulting data set to be an Array containing the emoji names
|
||||
# in descending order, based on how often they were awarded.
|
||||
AwardEmoji
|
||||
.award_counts_for_user(current_user)
|
||||
.map { |name, _| { name: name } }
|
||||
end
|
||||
end
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
class MoveToProjectFinder
|
||||
PAGE_SIZE = 50
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
def execute(from_project, search: nil, offset_id: nil)
|
||||
projects = @user.projects_where_can_admin_issues
|
||||
projects = projects.search(search) if search.present?
|
||||
projects = projects.excluding_project(from_project)
|
||||
projects = projects.order_id_desc
|
||||
|
||||
# infinite scroll using offset
|
||||
projects = projects.where('projects.id < ?', offset_id) if offset_id.present?
|
||||
projects = projects.limit(PAGE_SIZE)
|
||||
|
||||
# to ask for Project#name_with_namespace
|
||||
projects.includes(namespace: :owner)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# A simple finding for obtaining a single User.
|
||||
#
|
||||
# While using `User.find_by` directly is straightforward, it can lead to a lot
|
||||
# of code duplication. Sometimes we just want to find a user by an ID, other
|
||||
# times we may want to exclude blocked user. By using this finder (and extending
|
||||
# it whenever necessary) we can keep this logic in one place.
|
||||
class UserFinder
|
||||
attr_reader :params
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
# Tries to find a User, returning nil if none could be found.
|
||||
def execute
|
||||
User.find_by(id: params[:id])
|
||||
end
|
||||
|
||||
# Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could
|
||||
# not be found.
|
||||
def execute!
|
||||
User.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
|
@ -28,6 +28,23 @@ class AwardEmoji < ActiveRecord::Base
|
|||
.where('name IN (?) AND awardable_type = ? AND awardable_id IN (?)', [DOWNVOTE_NAME, UPVOTE_NAME], type, ids)
|
||||
.group('name', 'awardable_id')
|
||||
end
|
||||
|
||||
# Returns the top 100 emoji awarded by the given user.
|
||||
#
|
||||
# The returned value is a Hash mapping emoji names to the number of times
|
||||
# they were awarded:
|
||||
#
|
||||
# { 'thumbsup' => 2, 'thumbsdown' => 1 }
|
||||
#
|
||||
# user - The User to get the awards for.
|
||||
# limt - The maximum number of emoji to return.
|
||||
def award_counts_for_user(user, limit = 100)
|
||||
limit(limit)
|
||||
.where(user: user)
|
||||
.group(:name)
|
||||
.order('count_all DESC, name ASC')
|
||||
.count
|
||||
end
|
||||
end
|
||||
|
||||
def downvote?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module OptionallySearch
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
def search(*)
|
||||
raise(
|
||||
NotImplementedError,
|
||||
'Your model must implement the "search" class method'
|
||||
)
|
||||
end
|
||||
|
||||
# Optionally limits a result set to those matching the given search query.
|
||||
def optionally_search(query = nil)
|
||||
query.present? ? search(query) : all
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -28,6 +28,7 @@ class Project < ActiveRecord::Base
|
|||
include WithUploads
|
||||
include BatchDestroyDependentAssociations
|
||||
include FeatureGate
|
||||
include OptionallySearch
|
||||
extend Gitlab::Cache::RequestCache
|
||||
|
||||
extend Gitlab::ConfigHelper
|
||||
|
|
@ -384,6 +385,26 @@ class Project < ActiveRecord::Base
|
|||
only_integer: true,
|
||||
message: 'needs to be beetween 10 minutes and 1 month' }
|
||||
|
||||
# Paginates a collection using a `WHERE id < ?` condition.
|
||||
#
|
||||
# before - A project ID to use for filtering out projects with an equal or
|
||||
# greater ID. If no ID is given, all projects are included.
|
||||
#
|
||||
# limit - The maximum number of rows to include.
|
||||
def self.paginate_in_descending_order_using_id(
|
||||
before: nil,
|
||||
limit: Kaminari.config.default_per_page
|
||||
)
|
||||
relation = order_id_desc.limit(limit)
|
||||
relation = relation.where('projects.id < ?', before) if before
|
||||
|
||||
relation
|
||||
end
|
||||
|
||||
def self.eager_load_namespace_and_owner
|
||||
includes(namespace: :owner)
|
||||
end
|
||||
|
||||
# Returns a collection of projects that is either public or visible to the
|
||||
# logged in user.
|
||||
def self.public_or_visible_to_user(user = nil)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class User < ActiveRecord::Base
|
|||
include BulkMemberAccessLoad
|
||||
include BlocksJsonSerialization
|
||||
include WithUploads
|
||||
include OptionallySearch
|
||||
|
||||
DEFAULT_NOTIFICATION_LEVEL = :participating
|
||||
|
||||
|
|
@ -253,11 +254,41 @@ class User < ActiveRecord::Base
|
|||
scope :external, -> { where(external: true) }
|
||||
scope :active, -> { with_state(:active).non_internal }
|
||||
scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) }
|
||||
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
|
||||
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
|
||||
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
|
||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||
|
||||
# Limits the users to those that have TODOs, optionally in the given state.
|
||||
#
|
||||
# user - The user to get the todos for.
|
||||
#
|
||||
# with_todos - If we should limit the result set to users that are the
|
||||
# authors of todos.
|
||||
#
|
||||
# todo_state - An optional state to require the todos to be in.
|
||||
def self.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil)
|
||||
if user && with_todos
|
||||
where(id: Todo.where(user: user, state: todo_state).select(:author_id))
|
||||
else
|
||||
all
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a relation that optionally includes the given user.
|
||||
#
|
||||
# user_id - The ID of the user to include.
|
||||
def self.union_with_user(user_id = nil)
|
||||
if user_id.present?
|
||||
union = Gitlab::SQL::Union.new([all, User.unscoped.where(id: user_id)])
|
||||
|
||||
# We use "unscoped" here so that any inner conditions are not repeated for
|
||||
# the outer query, which would be redundant.
|
||||
User.unscoped.from("(#{union.to_sql}) #{User.table_name}")
|
||||
else
|
||||
all
|
||||
end
|
||||
end
|
||||
|
||||
def self.with_two_factor_indistinct
|
||||
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
|
||||
.where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true)
|
||||
|
|
@ -365,6 +396,18 @@ class User < ActiveRecord::Base
|
|||
).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
|
||||
end
|
||||
|
||||
# Limits the result set to users _not_ in the given query/list of IDs.
|
||||
#
|
||||
# users - The list of users to ignore. This can be an
|
||||
# `ActiveRecord::Relation`, or an Array.
|
||||
def where_not_in(users = nil)
|
||||
users ? where.not(id: users) : all
|
||||
end
|
||||
|
||||
def reorder_by_name
|
||||
reorder(:name)
|
||||
end
|
||||
|
||||
# searches user by given pattern
|
||||
# it compares name, email, username fields and user's secondary emails with given pattern
|
||||
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MoveToProjectEntity < Grape::Entity
|
||||
expose :id
|
||||
expose :name_with_namespace
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MoveToProjectSerializer < BaseSerializer
|
||||
entity MoveToProjectEntity
|
||||
end
|
||||
|
|
@ -13,7 +13,7 @@ module Gitlab
|
|||
@note = note
|
||||
@project = project
|
||||
@client = client
|
||||
@user_finder = UserFinder.new(project, client)
|
||||
@user_finder = GithubImport::UserFinder.new(project, client)
|
||||
end
|
||||
|
||||
def execute
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ module Gitlab
|
|||
@issue = issue
|
||||
@project = project
|
||||
@client = client
|
||||
@user_finder = UserFinder.new(project, client)
|
||||
@user_finder = GithubImport::UserFinder.new(project, client)
|
||||
@milestone_finder = MilestoneFinder.new(project)
|
||||
@issuable_finder = GithubImport::IssuableFinder.new(project, issue)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ module Gitlab
|
|||
@note = note
|
||||
@project = project
|
||||
@client = client
|
||||
@user_finder = UserFinder.new(project, client)
|
||||
@user_finder = GithubImport::UserFinder.new(project, client)
|
||||
end
|
||||
|
||||
def execute
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ module Gitlab
|
|||
@pull_request = pull_request
|
||||
@project = project
|
||||
@client = client
|
||||
@user_finder = UserFinder.new(project, client)
|
||||
@user_finder = GithubImport::UserFinder.new(project, client)
|
||||
@milestone_finder = MilestoneFinder.new(project)
|
||||
@issuable_finder =
|
||||
GithubImport::IssuableFinder.new(project, pull_request)
|
||||
|
|
|
|||
|
|
@ -274,14 +274,11 @@ describe AutocompleteController do
|
|||
|
||||
context 'authorized projects apply limit' do
|
||||
before do
|
||||
authorized_project2 = create(:project)
|
||||
authorized_project3 = create(:project)
|
||||
allow(Kaminari.config).to receive(:default_per_page).and_return(2)
|
||||
|
||||
authorized_project.add_maintainer(user)
|
||||
authorized_project2.add_maintainer(user)
|
||||
authorized_project3.add_maintainer(user)
|
||||
|
||||
stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
|
||||
create_list(:project, 2) do |project|
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #projects with project ID' do
|
||||
|
|
@ -291,7 +288,7 @@ describe AutocompleteController do
|
|||
|
||||
it 'returns projects' do
|
||||
expect(json_response).to be_kind_of(Array)
|
||||
expect(json_response.size).to eq 2 # Of a total of 3
|
||||
expect(json_response.size).to eq(Kaminari.config.default_per_page)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Autocomplete::GroupFinder do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
describe '#execute' do
|
||||
context 'with a project' do
|
||||
it 'returns nil' do
|
||||
project = create(:project)
|
||||
|
||||
expect(described_class.new(user, project).execute).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a group ID' do
|
||||
it 'returns nil' do
|
||||
expect(described_class.new(user).execute).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an empty String as the group ID' do
|
||||
it 'returns nil' do
|
||||
expect(described_class.new(user, nil, group_id: '').execute).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a project and with a group ID' do
|
||||
it 'raises ActiveRecord::RecordNotFound if the group does not exist' do
|
||||
finder = described_class.new(user, nil, group_id: 1)
|
||||
|
||||
expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it 'raises ActiveRecord::RecordNotFound if the user can not read the group' do
|
||||
group = create(:group, :private)
|
||||
finder = described_class.new(user, nil, group_id: group.id)
|
||||
|
||||
expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it 'raises ActiveRecord::RecordNotFound if an anonymous user can not read the group' do
|
||||
group = create(:group, :private)
|
||||
finder = described_class.new(nil, nil, group_id: group.id)
|
||||
|
||||
expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it 'returns the group if it exists and is readable' do
|
||||
group = create(:group)
|
||||
finder = described_class.new(user, nil, group_id: group.id)
|
||||
|
||||
expect(finder.execute).to eq(group)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe MoveToProjectFinder do
|
||||
describe Autocomplete::MoveToProjectFinder do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
|
||||
|
|
@ -10,14 +10,14 @@ describe MoveToProjectFinder do
|
|||
let(:developer_project) { create(:project) }
|
||||
let(:maintainer_project) { create(:project) }
|
||||
|
||||
subject { described_class.new(user) }
|
||||
|
||||
describe '#execute' do
|
||||
context 'filter' do
|
||||
it 'does not return projects under Gitlab::Access::REPORTER' do
|
||||
guest_project.add_guest(user)
|
||||
|
||||
expect(subject.execute(project)).to be_empty
|
||||
finder = described_class.new(user, project_id: project.id)
|
||||
|
||||
expect(finder.execute).to be_empty
|
||||
end
|
||||
|
||||
it 'returns projects equal or above Gitlab::Access::REPORTER ordered by id in descending order' do
|
||||
|
|
@ -25,13 +25,17 @@ describe MoveToProjectFinder do
|
|||
developer_project.add_developer(user)
|
||||
maintainer_project.add_maintainer(user)
|
||||
|
||||
expect(subject.execute(project).to_a).to eq([maintainer_project, developer_project, reporter_project])
|
||||
finder = described_class.new(user, project_id: project.id)
|
||||
|
||||
expect(finder.execute.to_a).to eq([maintainer_project, developer_project, reporter_project])
|
||||
end
|
||||
|
||||
it 'does not include the source project' do
|
||||
project.add_reporter(user)
|
||||
|
||||
expect(subject.execute(project).to_a).to be_empty
|
||||
finder = described_class.new(user, project_id: project.id)
|
||||
|
||||
expect(finder.execute.to_a).to be_empty
|
||||
end
|
||||
|
||||
it 'does not return archived projects' do
|
||||
|
|
@ -40,7 +44,9 @@ describe MoveToProjectFinder do
|
|||
other_reporter_project = create(:project)
|
||||
other_reporter_project.add_reporter(user)
|
||||
|
||||
expect(subject.execute(project).to_a).to eq([other_reporter_project])
|
||||
finder = described_class.new(user, project_id: project.id)
|
||||
|
||||
expect(finder.execute.to_a).to eq([other_reporter_project])
|
||||
end
|
||||
|
||||
it 'does not return projects for which issues are disabled' do
|
||||
|
|
@ -49,39 +55,42 @@ describe MoveToProjectFinder do
|
|||
other_reporter_project = create(:project)
|
||||
other_reporter_project.add_reporter(user)
|
||||
|
||||
expect(subject.execute(project).to_a).to eq([other_reporter_project])
|
||||
finder = described_class.new(user, project_id: project.id)
|
||||
|
||||
expect(finder.execute.to_a).to eq([other_reporter_project])
|
||||
end
|
||||
|
||||
it 'returns a page of projects ordered by id in descending order' do
|
||||
stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
|
||||
allow(Kaminari.config).to receive(:default_per_page).and_return(2)
|
||||
|
||||
reporter_project.add_reporter(user)
|
||||
developer_project.add_developer(user)
|
||||
maintainer_project.add_maintainer(user)
|
||||
projects = create_list(:project, 2) do |project|
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
expect(subject.execute(project).to_a).to eq([maintainer_project, developer_project])
|
||||
finder = described_class.new(user, project_id: project.id)
|
||||
page = finder.execute.to_a
|
||||
|
||||
expect(page.length).to eq(Kaminari.config.default_per_page)
|
||||
expect(page[0]).to eq(projects.last)
|
||||
end
|
||||
|
||||
it 'returns projects after the given offset id' do
|
||||
stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
|
||||
|
||||
reporter_project.add_reporter(user)
|
||||
developer_project.add_developer(user)
|
||||
maintainer_project.add_maintainer(user)
|
||||
|
||||
expect(subject.execute(project, search: nil, offset_id: maintainer_project.id).to_a).to eq([developer_project, reporter_project])
|
||||
expect(subject.execute(project, search: nil, offset_id: developer_project.id).to_a).to eq([reporter_project])
|
||||
expect(subject.execute(project, search: nil, offset_id: reporter_project.id).to_a).to be_empty
|
||||
expect(described_class.new(user, project_id: project.id, offset_id: maintainer_project.id).execute.to_a)
|
||||
.to eq([developer_project, reporter_project])
|
||||
|
||||
expect(described_class.new(user, project_id: project.id, offset_id: developer_project.id).execute.to_a)
|
||||
.to eq([reporter_project])
|
||||
|
||||
expect(described_class.new(user, project_id: project.id, offset_id: reporter_project.id).execute.to_a)
|
||||
.to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'search' do
|
||||
it 'uses Project#search' do
|
||||
expect(user).to receive_message_chain(:projects_where_can_admin_issues, :search) { Project.all }
|
||||
|
||||
subject.execute(project, search: 'wadus')
|
||||
end
|
||||
|
||||
it 'returns projects matching a search query' do
|
||||
foo_project = create(:project)
|
||||
foo_project.add_maintainer(user)
|
||||
|
|
@ -89,8 +98,11 @@ describe MoveToProjectFinder do
|
|||
wadus_project = create(:project, name: 'wadus')
|
||||
wadus_project.add_maintainer(user)
|
||||
|
||||
expect(subject.execute(project).to_a).to eq([wadus_project, foo_project])
|
||||
expect(subject.execute(project, search: 'wadus').to_a).to eq([wadus_project])
|
||||
expect(described_class.new(user, project_id: project.id).execute.to_a)
|
||||
.to eq([wadus_project, foo_project])
|
||||
|
||||
expect(described_class.new(user, project_id: project.id, search: 'wadus').execute.to_a)
|
||||
.to eq([wadus_project])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Autocomplete::ProjectFinder do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
describe '#execute' do
|
||||
context 'without a project ID' do
|
||||
it 'returns nil' do
|
||||
expect(described_class.new(user).execute).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an empty String as the project ID' do
|
||||
it 'returns nil' do
|
||||
expect(described_class.new(user, project_id: '').execute).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a project ID' do
|
||||
it 'raises ActiveRecord::RecordNotFound if the project does not exist' do
|
||||
finder = described_class.new(user, project_id: 1)
|
||||
|
||||
expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it 'raises ActiveRecord::RecordNotFound if the user can not read the project' do
|
||||
project = create(:project, :private)
|
||||
|
||||
finder = described_class.new(user, project_id: project.id)
|
||||
|
||||
expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it 'raises ActiveRecord::RecordNotFound if an anonymous user can not read the project' do
|
||||
project = create(:project, :private)
|
||||
|
||||
finder = described_class.new(nil, project_id: project.id)
|
||||
|
||||
expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it 'returns the project if it exists and is readable' do
|
||||
project = create(:project, :private)
|
||||
|
||||
project.add_maintainer(user)
|
||||
|
||||
finder = described_class.new(user, project_id: project.id)
|
||||
|
||||
expect(finder.execute).to eq(project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe AutocompleteUsersFinder do
|
||||
describe Autocomplete::UsersFinder do
|
||||
describe '#execute' do
|
||||
let!(:user1) { create(:user, username: 'johndoe') }
|
||||
let!(:user2) { create(:user, :blocked, username: 'notsorandom') }
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe AwardedEmojiFinder do
|
||||
describe '#execute' do
|
||||
it 'returns an Array containing the awarded emoji names' do
|
||||
user = create(:user)
|
||||
|
||||
create(:award_emoji, user: user, name: 'thumbsup')
|
||||
create(:award_emoji, user: user, name: 'thumbsup')
|
||||
create(:award_emoji, user: user, name: 'thumbsdown')
|
||||
|
||||
awarded = described_class.new(user).execute
|
||||
|
||||
expect(awarded).to eq([{ name: 'thumbsup' }, { name: 'thumbsdown' }])
|
||||
end
|
||||
|
||||
it 'returns an empty Array when no user is given' do
|
||||
awarded = described_class.new.execute
|
||||
|
||||
expect(awarded).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe UserFinder do
|
||||
describe '#execute' do
|
||||
context 'when the user exists' do
|
||||
it 'returns the user' do
|
||||
user = create(:user)
|
||||
found = described_class.new(id: user.id).execute
|
||||
|
||||
expect(found).to eq(user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user does not exist' do
|
||||
it 'returns nil' do
|
||||
found = described_class.new(id: 1).execute
|
||||
|
||||
expect(found).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute!' do
|
||||
context 'when the user exists' do
|
||||
it 'returns the user' do
|
||||
user = create(:user)
|
||||
found = described_class.new(id: user.id).execute!
|
||||
|
||||
expect(found).to eq(user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user does not exist' do
|
||||
it 'raises ActiveRecord::RecordNotFound' do
|
||||
finder = described_class.new(id: 1)
|
||||
|
||||
expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -77,4 +77,27 @@ describe AwardEmoji do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.award_counts_for_user' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
create(:award_emoji, user: user, name: 'thumbsup')
|
||||
create(:award_emoji, user: user, name: 'thumbsup')
|
||||
create(:award_emoji, user: user, name: 'thumbsdown')
|
||||
create(:award_emoji, user: user, name: '+1')
|
||||
end
|
||||
|
||||
it 'returns the awarded emoji in descending order' do
|
||||
awards = described_class.award_counts_for_user(user)
|
||||
|
||||
expect(awards).to eq('thumbsup' => 2, 'thumbsdown' => 1, '+1' => 1)
|
||||
end
|
||||
|
||||
it 'limits the returned number of rows' do
|
||||
awards = described_class.award_counts_for_user(user, 1)
|
||||
|
||||
expect(awards).to eq('thumbsup' => 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe OptionallySearch do
|
||||
let(:model) do
|
||||
Class.new(ActiveRecord::Base) do
|
||||
self.table_name = 'users'
|
||||
|
||||
include OptionallySearch
|
||||
end
|
||||
end
|
||||
|
||||
describe '.search' do
|
||||
it 'raises NotImplementedError' do
|
||||
expect { model.search('foo') }.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.optionally_search' do
|
||||
context 'when a query is given' do
|
||||
it 'delegates to the search method' do
|
||||
expect(model)
|
||||
.to receive(:search)
|
||||
.with('foo')
|
||||
|
||||
model.optionally_search('foo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no query is given' do
|
||||
it 'returns the current relation' do
|
||||
expect(model.optionally_search).to be_a_kind_of(ActiveRecord::Relation)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an empty query is given' do
|
||||
it 'returns the current relation' do
|
||||
expect(model.optionally_search(''))
|
||||
.to be_a_kind_of(ActiveRecord::Relation)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1478,6 +1478,53 @@ describe Project do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.optionally_search' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'searches for projects matching the query if one is given' do
|
||||
relation = described_class.optionally_search(project.name)
|
||||
|
||||
expect(relation).to eq([project])
|
||||
end
|
||||
|
||||
it 'returns the current relation if no search query is given' do
|
||||
relation = described_class.where(id: project.id)
|
||||
|
||||
expect(relation.optionally_search).to eq(relation)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.paginate_in_descending_order_using_id' do
|
||||
let!(:project1) { create(:project) }
|
||||
let!(:project2) { create(:project) }
|
||||
|
||||
it 'orders the relation in descending order' do
|
||||
expect(described_class.paginate_in_descending_order_using_id)
|
||||
.to eq([project2, project1])
|
||||
end
|
||||
|
||||
it 'applies a limit to the relation' do
|
||||
expect(described_class.paginate_in_descending_order_using_id(limit: 1))
|
||||
.to eq([project2])
|
||||
end
|
||||
|
||||
it 'limits projects by and ID when given' do
|
||||
expect(described_class.paginate_in_descending_order_using_id(before: project2.id))
|
||||
.to eq([project1])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.including_namespace_and_owner' do
|
||||
it 'eager loads the namespace and namespace owner' do
|
||||
create(:project)
|
||||
|
||||
row = described_class.eager_load_namespace_and_owner.to_a.first
|
||||
recorder = ActiveRecord::QueryRecorder.new { row.namespace.owner }
|
||||
|
||||
expect(recorder.count).to be_zero
|
||||
end
|
||||
end
|
||||
|
||||
describe '#expire_caches_before_rename' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:repo) { double(:repo, exists?: true) }
|
||||
|
|
|
|||
|
|
@ -346,17 +346,55 @@ describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.todo_authors' do
|
||||
it 'filters users' do
|
||||
create :user
|
||||
user_2 = create :user
|
||||
user_3 = create :user
|
||||
current_user = create :user
|
||||
create(:todo, user: current_user, author: user_2, state: :done)
|
||||
create(:todo, user: current_user, author: user_3, state: :pending)
|
||||
describe '.limit_to_todo_authors' do
|
||||
context 'when filtering by todo authors' do
|
||||
let(:user1) { create(:user) }
|
||||
let(:user2) { create(:user) }
|
||||
|
||||
expect(described_class.todo_authors(current_user.id, 'pending')).to eq [user_3]
|
||||
expect(described_class.todo_authors(current_user.id, 'done')).to eq [user_2]
|
||||
before do
|
||||
create(:todo, user: user1, author: user1, state: :done)
|
||||
create(:todo, user: user2, author: user2, state: :pending)
|
||||
end
|
||||
|
||||
it 'only returns users that have authored todos' do
|
||||
users = described_class.limit_to_todo_authors(
|
||||
user: user2,
|
||||
with_todos: true,
|
||||
todo_state: :pending
|
||||
)
|
||||
|
||||
expect(users).to eq([user2])
|
||||
end
|
||||
|
||||
it 'ignores users that do not have a todo in the matching state' do
|
||||
users = described_class.limit_to_todo_authors(
|
||||
user: user1,
|
||||
with_todos: true,
|
||||
todo_state: :pending
|
||||
)
|
||||
|
||||
expect(users).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not filtering by todo authors' do
|
||||
it 'returns the input relation' do
|
||||
user1 = create(:user)
|
||||
user2 = create(:user)
|
||||
rel = described_class.limit_to_todo_authors(user: user1)
|
||||
|
||||
expect(rel).to include(user1, user2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no user is provided' do
|
||||
it 'returns the input relation' do
|
||||
user1 = create(:user)
|
||||
user2 = create(:user)
|
||||
rel = described_class.limit_to_todo_authors
|
||||
|
||||
expect(rel).to include(user1, user2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2901,4 +2939,86 @@ describe User do
|
|||
let(:uploader_class) { AttachmentUploader }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.union_with_user' do
|
||||
context 'when no user ID is provided' do
|
||||
it 'returns the input relation' do
|
||||
user = create(:user)
|
||||
|
||||
expect(described_class.union_with_user).to eq([user])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user ID is provided' do
|
||||
it 'includes the user object in the returned relation' do
|
||||
user1 = create(:user)
|
||||
user2 = create(:user)
|
||||
users = described_class.where(id: user1.id).union_with_user(user2.id)
|
||||
|
||||
expect(users).to include(user1)
|
||||
expect(users).to include(user2)
|
||||
end
|
||||
|
||||
it 'does not re-apply any WHERE conditions on the outer query' do
|
||||
relation = described_class.where(id: 1).union_with_user(2)
|
||||
|
||||
expect(relation.arel.where_sql).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.optionally_search' do
|
||||
context 'using nil as the argument' do
|
||||
it 'returns the current relation' do
|
||||
user = create(:user)
|
||||
|
||||
expect(described_class.optionally_search).to eq([user])
|
||||
end
|
||||
end
|
||||
|
||||
context 'using an empty String as the argument' do
|
||||
it 'returns the current relation' do
|
||||
user = create(:user)
|
||||
|
||||
expect(described_class.optionally_search('')).to eq([user])
|
||||
end
|
||||
end
|
||||
|
||||
context 'using a non-empty String' do
|
||||
it 'returns users matching the search query' do
|
||||
user1 = create(:user)
|
||||
create(:user)
|
||||
|
||||
expect(described_class.optionally_search(user1.name)).to eq([user1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.where_not_in' do
|
||||
context 'without an argument' do
|
||||
it 'returns the current relation' do
|
||||
user = create(:user)
|
||||
|
||||
expect(described_class.where_not_in).to eq([user])
|
||||
end
|
||||
end
|
||||
|
||||
context 'using a list of user IDs' do
|
||||
it 'excludes the users from the returned relation' do
|
||||
user1 = create(:user)
|
||||
user2 = create(:user)
|
||||
|
||||
expect(described_class.where_not_in([user2.id])).to eq([user1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.reorder_by_name' do
|
||||
it 'reorders the input relation' do
|
||||
user1 = create(:user, name: 'A')
|
||||
user2 = create(:user, name: 'B')
|
||||
|
||||
expect(described_class.reorder_by_name).to eq([user1, user2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe MoveToProjectEntity do
|
||||
describe '#as_json' do
|
||||
let(:project) { build(:project, id: 1) }
|
||||
|
||||
subject { described_class.new(project).as_json }
|
||||
|
||||
it 'includes the project ID' do
|
||||
expect(subject[:id]).to eq(project.id)
|
||||
end
|
||||
|
||||
it 'includes the full path' do
|
||||
expect(subject[:name_with_namespace]).to eq(project.name_with_namespace)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe MoveToProjectSerializer do
|
||||
describe '#represent' do
|
||||
it 'includes the name and name with namespace' do
|
||||
project = build(:project, id: 1)
|
||||
output = described_class.new.represent(project)
|
||||
|
||||
expect(output).to include(:id, :name_with_namespace)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue