2019-12-04 20:38:33 +05:30
# frozen_string_literal: true
2016-06-02 11:05:42 +05:30
require 'spec_helper'
2020-07-28 23:09:34 +05:30
RSpec . describe Gitlab :: SearchResults do
2018-03-17 18:26:18 +05:30
include ProjectForksHelper
2019-12-04 20:38:33 +05:30
include SearchHelpers
2021-03-11 19:13:27 +05:30
using RSpec :: Parameterized :: TableSyntax
2018-03-17 18:26:18 +05:30
2020-11-24 15:15:51 +05:30
let_it_be ( :user ) { create ( :user ) }
let_it_be ( :project ) { create ( :project , name : 'foo' ) }
let_it_be ( :issue ) { create ( :issue , project : project , title : 'foo' ) }
let_it_be ( :milestone ) { create ( :milestone , project : project , title : 'foo' ) }
2021-09-30 23:02:18 +05:30
2020-11-24 15:15:51 +05:30
let ( :merge_request ) { create ( :merge_request , source_project : project , title : 'foo' ) }
2021-01-03 14:25:43 +05:30
let ( :query ) { 'foo' }
2020-11-24 15:15:51 +05:30
let ( :filters ) { { } }
2021-01-03 14:25:43 +05:30
let ( :sort ) { nil }
2016-06-02 11:05:42 +05:30
2021-01-03 14:25:43 +05:30
subject ( :results ) { described_class . new ( user , query , Project . order ( :id ) , sort : sort , filters : filters ) }
2016-06-02 11:05:42 +05:30
2017-01-15 13:20:01 +05:30
context 'as a user with access' do
before do
2018-03-17 18:26:18 +05:30
project . add_developer ( user )
end
describe '#objects' do
it 'returns without_counts collection by default' do
expect ( results . objects ( 'projects' ) ) . to be_kind_of ( Kaminari :: PaginatableWithoutCount )
end
it 'returns with counts collection when requested' do
2020-05-24 23:13:21 +05:30
expect ( results . objects ( 'projects' , page : 1 , per_page : 1 , without_count : false ) ) . not_to be_kind_of ( Kaminari :: PaginatableWithoutCount )
end
it 'uses page and per_page to paginate results' do
project2 = create ( :project , name : 'foo' )
expect ( results . objects ( 'projects' , page : 1 , per_page : 1 ) . to_a ) . to eq ( [ project ] )
expect ( results . objects ( 'projects' , page : 2 , per_page : 1 ) . to_a ) . to eq ( [ project2 ] )
expect ( results . objects ( 'projects' , page : 1 , per_page : 2 ) . count ) . to eq ( 2 )
2018-03-17 18:26:18 +05:30
end
2016-06-02 11:05:42 +05:30
end
2019-10-12 21:52:04 +05:30
describe '#formatted_count' do
where ( :scope , :count_method , :expected ) do
2019-12-04 20:38:33 +05:30
'projects' | :limited_projects_count | max_limited_count
'issues' | :limited_issues_count | max_limited_count
'merge_requests' | :limited_merge_requests_count | max_limited_count
'milestones' | :limited_milestones_count | max_limited_count
'users' | :limited_users_count | max_limited_count
2019-10-12 21:52:04 +05:30
'unknown' | nil | nil
end
with_them do
it 'returns the expected formatted count' do
expect ( results ) . to receive ( count_method ) . and_return ( 1234 ) if count_method
expect ( results . formatted_count ( scope ) ) . to eq ( expected )
end
end
end
2021-01-03 14:25:43 +05:30
describe '#highlight_map' do
where ( :scope , :expected ) do
'projects' | { }
'issues' | { }
'merge_requests' | { }
'milestones' | { }
'users' | { }
'unknown' | { }
end
with_them do
it 'returns the expected highlight_map' do
expect ( results . highlight_map ( scope ) ) . to eq ( expected )
end
end
end
2019-10-12 21:52:04 +05:30
describe '#formatted_limited_count' do
where ( :count , :expected ) do
23 | '23'
2019-12-21 20:55:43 +05:30
99 | '99'
100 | max_limited_count
2019-12-04 20:38:33 +05:30
1234 | max_limited_count
2019-10-12 21:52:04 +05:30
end
with_them do
it 'returns the expected formatted limited count' do
expect ( results . formatted_limited_count ( count ) ) . to eq ( expected )
end
end
end
2018-03-17 18:26:18 +05:30
context " when count_limit is lower than total amount " do
before do
allow ( results ) . to receive ( :count_limit ) . and_return ( 1 )
end
describe '#limited_projects_count' do
it 'returns the limited amount of projects' do
create ( :project , name : 'foo2' )
expect ( results . limited_projects_count ) . to eq ( 1 )
end
end
describe '#limited_merge_requests_count' do
it 'returns the limited amount of merge requests' do
create ( :merge_request , :simple , source_project : project , title : 'foo2' )
expect ( results . limited_merge_requests_count ) . to eq ( 1 )
end
end
describe '#limited_milestones_count' do
it 'returns the limited amount of milestones' do
create ( :milestone , project : project , title : 'foo2' )
expect ( results . limited_milestones_count ) . to eq ( 1 )
end
end
describe '#limited_issues_count' do
it 'runs single SQL query to get the limited amount of issues' do
2020-11-24 15:15:51 +05:30
create ( :issue , project : project , title : 'foo2' )
2018-03-17 18:26:18 +05:30
expect ( results ) . to receive ( :issues ) . with ( public_only : true ) . and_call_original
2020-11-24 15:15:51 +05:30
expect ( results ) . not_to receive ( :issues ) . with ( no_args )
2018-03-17 18:26:18 +05:30
expect ( results . limited_issues_count ) . to eq ( 1 )
end
end
end
context " when count_limit is higher than total amount " do
describe '#limited_issues_count' do
it 'runs multiple queries to get the limited amount of issues' do
expect ( results ) . to receive ( :issues ) . with ( public_only : true ) . and_call_original
expect ( results ) . to receive ( :issues ) . with ( no_args ) . and_call_original
expect ( results . limited_issues_count ) . to eq ( 1 )
end
end
end
2017-01-15 13:20:01 +05:30
it 'includes merge requests from source and target projects' do
2018-03-17 18:26:18 +05:30
forked_project = fork_project ( project , user )
2017-01-15 13:20:01 +05:30
merge_request_2 = create ( :merge_request , target_project : project , source_project : forked_project , title : 'foo' )
2020-11-24 15:15:51 +05:30
results = described_class . new ( user , 'foo' , Project . where ( id : forked_project . id ) )
2017-01-15 13:20:01 +05:30
expect ( results . objects ( 'merge_requests' ) ) . to include merge_request_2
end
2018-03-17 18:26:18 +05:30
describe '#merge_requests' do
2021-01-03 14:25:43 +05:30
let ( :scope ) { 'merge_requests' }
2018-03-17 18:26:18 +05:30
it 'includes project filter by default' do
expect ( results ) . to receive ( :project_ids_relation ) . and_call_original
2021-01-03 14:25:43 +05:30
results . objects ( scope )
2018-03-17 18:26:18 +05:30
end
2019-07-07 11:18:12 +05:30
it 'skips project filter if default project context is used' do
2018-03-17 18:26:18 +05:30
allow ( results ) . to receive ( :default_project_filter ) . and_return ( true )
expect ( results ) . not_to receive ( :project_ids_relation )
2021-01-03 14:25:43 +05:30
results . objects ( scope )
2018-03-17 18:26:18 +05:30
end
2020-11-24 15:15:51 +05:30
context 'filtering' do
let! ( :opened_result ) { create ( :merge_request , :opened , source_project : project , title : 'foo opened' ) }
let! ( :closed_result ) { create ( :merge_request , :closed , source_project : project , title : 'foo closed' ) }
let ( :query ) { 'foo' }
include_examples 'search results filtered by state'
end
2021-01-03 14:25:43 +05:30
context 'ordering' do
let! ( :old_result ) { create ( :merge_request , :opened , source_project : project , source_branch : 'old-1' , title : 'sorted old' , created_at : 1 . month . ago ) }
let! ( :new_result ) { create ( :merge_request , :opened , source_project : project , source_branch : 'new-1' , title : 'sorted recent' , created_at : 1 . day . ago ) }
let! ( :very_old_result ) { create ( :merge_request , :opened , source_project : project , source_branch : 'very-old-1' , title : 'sorted very old' , created_at : 1 . year . ago ) }
2021-03-11 19:13:27 +05:30
let! ( :old_updated ) { create ( :merge_request , :opened , source_project : project , source_branch : 'updated-old-1' , title : 'updated old' , updated_at : 1 . month . ago ) }
let! ( :new_updated ) { create ( :merge_request , :opened , source_project : project , source_branch : 'updated-new-1' , title : 'updated recent' , updated_at : 1 . day . ago ) }
let! ( :very_old_updated ) { create ( :merge_request , :opened , source_project : project , source_branch : 'updated-very-old-1' , title : 'updated very old' , updated_at : 1 . year . ago ) }
include_examples 'search results sorted' do
let ( :results_created ) { described_class . new ( user , 'sorted' , Project . order ( :id ) , sort : sort , filters : filters ) }
let ( :results_updated ) { described_class . new ( user , 'updated' , Project . order ( :id ) , sort : sort , filters : filters ) }
end
2021-01-03 14:25:43 +05:30
end
2018-03-17 18:26:18 +05:30
end
describe '#issues' do
2021-01-03 14:25:43 +05:30
let ( :scope ) { 'issues' }
2018-03-17 18:26:18 +05:30
it 'includes project filter by default' do
expect ( results ) . to receive ( :project_ids_relation ) . and_call_original
2021-01-03 14:25:43 +05:30
results . objects ( scope )
2018-03-17 18:26:18 +05:30
end
2019-07-07 11:18:12 +05:30
it 'skips project filter if default project context is used' do
2018-03-17 18:26:18 +05:30
allow ( results ) . to receive ( :default_project_filter ) . and_return ( true )
expect ( results ) . not_to receive ( :project_ids_relation )
2021-01-03 14:25:43 +05:30
results . objects ( scope )
2018-03-17 18:26:18 +05:30
end
2020-11-24 15:15:51 +05:30
context 'filtering' do
let_it_be ( :closed_result ) { create ( :issue , :closed , project : project , title : 'foo closed' ) }
let_it_be ( :opened_result ) { create ( :issue , :opened , project : project , title : 'foo open' ) }
2021-01-03 14:25:43 +05:30
let_it_be ( :confidential_result ) { create ( :issue , :confidential , project : project , title : 'foo confidential' ) }
2020-11-24 15:15:51 +05:30
include_examples 'search results filtered by state'
2021-01-03 14:25:43 +05:30
include_examples 'search results filtered by confidential'
end
context 'ordering' do
let! ( :old_result ) { create ( :issue , project : project , title : 'sorted old' , created_at : 1 . month . ago ) }
let! ( :new_result ) { create ( :issue , project : project , title : 'sorted recent' , created_at : 1 . day . ago ) }
let! ( :very_old_result ) { create ( :issue , project : project , title : 'sorted very old' , created_at : 1 . year . ago ) }
2021-03-11 19:13:27 +05:30
let! ( :old_updated ) { create ( :issue , project : project , title : 'updated old' , updated_at : 1 . month . ago ) }
let! ( :new_updated ) { create ( :issue , project : project , title : 'updated recent' , updated_at : 1 . day . ago ) }
let! ( :very_old_updated ) { create ( :issue , project : project , title : 'updated very old' , updated_at : 1 . year . ago ) }
2021-09-30 23:02:18 +05:30
let! ( :less_popular_result ) { create ( :issue , project : project , title : 'less popular' , upvotes_count : 10 ) }
let! ( :popular_result ) { create ( :issue , project : project , title : 'popular' , upvotes_count : 100 ) }
let! ( :non_popular_result ) { create ( :issue , project : project , title : 'non popular' , upvotes_count : 1 ) }
2021-03-11 19:13:27 +05:30
include_examples 'search results sorted' do
let ( :results_created ) { described_class . new ( user , 'sorted' , Project . order ( :id ) , sort : sort , filters : filters ) }
let ( :results_updated ) { described_class . new ( user , 'updated' , Project . order ( :id ) , sort : sort , filters : filters ) }
end
2021-09-30 23:02:18 +05:30
include_examples 'search results sorted by popularity' do
let ( :results_popular ) { described_class . new ( user , 'popular' , Project . order ( :id ) , sort : sort , filters : filters ) }
end
2020-11-24 15:15:51 +05:30
end
2018-03-17 18:26:18 +05:30
end
2019-07-07 11:18:12 +05:30
describe '#users' do
it 'does not call the UsersFinder when the current_user is not allowed to read users list' do
allow ( Ability ) . to receive ( :allowed? ) . and_return ( false )
expect ( UsersFinder ) . not_to receive ( :new ) . with ( user , search : 'foo' ) . and_call_original
results . objects ( 'users' )
end
it 'calls the UsersFinder' do
expect ( UsersFinder ) . to receive ( :new ) . with ( user , search : 'foo' ) . and_call_original
results . objects ( 'users' )
end
end
2017-01-15 13:20:01 +05:30
end
it 'does not list issues on private projects' do
2017-09-10 17:25:29 +05:30
private_project = create ( :project , :private )
2017-01-15 13:20:01 +05:30
issue = create ( :issue , project : private_project , title : 'foo' )
expect ( results . objects ( 'issues' ) ) . not_to include issue
2016-06-02 11:05:42 +05:30
end
describe 'confidential issues' do
2017-09-10 17:25:29 +05:30
let ( :project_1 ) { create ( :project , :internal ) }
let ( :project_2 ) { create ( :project , :internal ) }
let ( :project_3 ) { create ( :project , :internal ) }
let ( :project_4 ) { create ( :project , :internal ) }
2016-06-02 11:05:42 +05:30
let ( :query ) { 'issue' }
let ( :limit_projects ) { Project . where ( id : [ project_1 . id , project_2 . id , project_3 . id ] ) }
let ( :author ) { create ( :user ) }
let ( :assignee ) { create ( :user ) }
let ( :non_member ) { create ( :user ) }
let ( :member ) { create ( :user ) }
let ( :admin ) { create ( :admin ) }
let! ( :issue ) { create ( :issue , project : project_1 , title : 'Issue 1' ) }
let! ( :security_issue_1 ) { create ( :issue , :confidential , project : project_1 , title : 'Security issue 1' , author : author ) }
2017-08-17 22:00:37 +05:30
let! ( :security_issue_2 ) { create ( :issue , :confidential , title : 'Security issue 2' , project : project_1 , assignees : [ assignee ] ) }
2016-06-02 11:05:42 +05:30
let! ( :security_issue_3 ) { create ( :issue , :confidential , project : project_2 , title : 'Security issue 3' , author : author ) }
2017-08-17 22:00:37 +05:30
let! ( :security_issue_4 ) { create ( :issue , :confidential , project : project_3 , title : 'Security issue 4' , assignees : [ assignee ] ) }
2016-06-02 11:05:42 +05:30
let! ( :security_issue_5 ) { create ( :issue , :confidential , project : project_4 , title : 'Security issue 5' ) }
2016-09-13 17:45:13 +05:30
it 'does not list confidential issues for non project members' do
2020-11-24 15:15:51 +05:30
results = described_class . new ( non_member , query , limit_projects )
2016-06-02 11:05:42 +05:30
issues = results . objects ( 'issues' )
expect ( issues ) . to include issue
expect ( issues ) . not_to include security_issue_1
expect ( issues ) . not_to include security_issue_2
expect ( issues ) . not_to include security_issue_3
expect ( issues ) . not_to include security_issue_4
expect ( issues ) . not_to include security_issue_5
2018-03-27 19:54:05 +05:30
expect ( results . limited_issues_count ) . to eq 1
2016-06-02 11:05:42 +05:30
end
2016-09-13 17:45:13 +05:30
it 'does not list confidential issues for project members with guest role' do
2018-03-17 18:26:18 +05:30
project_1 . add_guest ( member )
project_2 . add_guest ( member )
2016-06-16 23:09:34 +05:30
2020-11-24 15:15:51 +05:30
results = described_class . new ( member , query , limit_projects )
2016-06-16 23:09:34 +05:30
issues = results . objects ( 'issues' )
expect ( issues ) . to include issue
expect ( issues ) . not_to include security_issue_1
expect ( issues ) . not_to include security_issue_2
expect ( issues ) . not_to include security_issue_3
expect ( issues ) . not_to include security_issue_4
expect ( issues ) . not_to include security_issue_5
2018-03-27 19:54:05 +05:30
expect ( results . limited_issues_count ) . to eq 1
2016-06-16 23:09:34 +05:30
end
2016-09-13 17:45:13 +05:30
it 'lists confidential issues for author' do
2020-11-24 15:15:51 +05:30
results = described_class . new ( author , query , limit_projects )
2016-06-02 11:05:42 +05:30
issues = results . objects ( 'issues' )
expect ( issues ) . to include issue
expect ( issues ) . to include security_issue_1
expect ( issues ) . not_to include security_issue_2
expect ( issues ) . to include security_issue_3
expect ( issues ) . not_to include security_issue_4
expect ( issues ) . not_to include security_issue_5
2018-03-27 19:54:05 +05:30
expect ( results . limited_issues_count ) . to eq 3
2016-06-02 11:05:42 +05:30
end
2016-09-13 17:45:13 +05:30
it 'lists confidential issues for assignee' do
2020-11-24 15:15:51 +05:30
results = described_class . new ( assignee , query , limit_projects )
2016-06-02 11:05:42 +05:30
issues = results . objects ( 'issues' )
expect ( issues ) . to include issue
expect ( issues ) . not_to include security_issue_1
expect ( issues ) . to include security_issue_2
expect ( issues ) . not_to include security_issue_3
expect ( issues ) . to include security_issue_4
expect ( issues ) . not_to include security_issue_5
2018-03-27 19:54:05 +05:30
expect ( results . limited_issues_count ) . to eq 3
2016-06-02 11:05:42 +05:30
end
2016-09-13 17:45:13 +05:30
it 'lists confidential issues for project members' do
2018-03-17 18:26:18 +05:30
project_1 . add_developer ( member )
project_2 . add_developer ( member )
2016-06-02 11:05:42 +05:30
2020-11-24 15:15:51 +05:30
results = described_class . new ( member , query , limit_projects )
2016-06-02 11:05:42 +05:30
issues = results . objects ( 'issues' )
expect ( issues ) . to include issue
expect ( issues ) . to include security_issue_1
expect ( issues ) . to include security_issue_2
expect ( issues ) . to include security_issue_3
expect ( issues ) . not_to include security_issue_4
expect ( issues ) . not_to include security_issue_5
2018-03-27 19:54:05 +05:30
expect ( results . limited_issues_count ) . to eq 4
2016-06-02 11:05:42 +05:30
end
2021-03-08 18:12:59 +05:30
context 'with admin user' do
context 'when admin mode enabled' , :enable_admin_mode do
it 'lists all issues' do
results = described_class . new ( admin , query , limit_projects )
issues = results . objects ( 'issues' )
expect ( issues ) . to include issue
expect ( issues ) . to include security_issue_1
expect ( issues ) . to include security_issue_2
expect ( issues ) . to include security_issue_3
expect ( issues ) . to include security_issue_4
expect ( issues ) . not_to include security_issue_5
expect ( results . limited_issues_count ) . to eq 5
end
end
2016-06-02 11:05:42 +05:30
2021-03-08 18:12:59 +05:30
context 'when admin mode disabled' do
it 'does not list confidential issues' do
results = described_class . new ( admin , query , limit_projects )
issues = results . objects ( 'issues' )
expect ( issues ) . to include issue
expect ( issues ) . not_to include security_issue_1
expect ( issues ) . not_to include security_issue_2
expect ( issues ) . not_to include security_issue_3
expect ( issues ) . not_to include security_issue_4
expect ( issues ) . not_to include security_issue_5
expect ( results . limited_issues_count ) . to eq 1
end
end
2016-06-02 11:05:42 +05:30
end
end
2017-01-15 13:20:01 +05:30
it 'does not list merge requests on projects with limited access' do
project . update! ( visibility_level : Gitlab :: VisibilityLevel :: PUBLIC )
project . project_feature . update! ( merge_requests_access_level : ProjectFeature :: PRIVATE )
expect ( results . objects ( 'merge_requests' ) ) . not_to include merge_request
end
2019-06-05 12:25:43 +05:30
context 'milestones' do
it 'returns correct set of milestones' do
private_project_1 = create ( :project , :private )
private_project_2 = create ( :project , :private )
internal_project = create ( :project , :internal )
public_project_1 = create ( :project , :public )
public_project_2 = create ( :project , :public , :issues_disabled , :merge_requests_disabled )
private_project_1 . add_developer ( user )
# milestones that should not be visible
create ( :milestone , project : private_project_2 , title : 'Private project without access milestone' )
create ( :milestone , project : public_project_2 , title : 'Public project with milestones disabled milestone' )
# milestones that should be visible
milestone_1 = create ( :milestone , project : private_project_1 , title : 'Private project with access milestone' , state : 'closed' )
milestone_2 = create ( :milestone , project : internal_project , title : 'Internal project milestone' )
milestone_3 = create ( :milestone , project : public_project_1 , title : 'Public project with milestones enabled milestone' )
# Global search scope takes user authorized projects, internal projects and public projects.
limit_projects = ProjectsFinder . new ( current_user : user ) . execute
2020-11-24 15:15:51 +05:30
milestones = described_class . new ( user , 'milestone' , limit_projects ) . objects ( 'milestones' )
2019-06-05 12:25:43 +05:30
expect ( milestones ) . to match_array ( [ milestone_1 , milestone_2 , milestone_3 ] )
end
end
2016-06-02 11:05:42 +05:30
end