From 56388dc9c245b320499b43722e225a3130b08144 Mon Sep 17 00:00:00 2001 From: Terri Chu <tchu@gitlab.com> Date: Thu, 13 Jul 2023 23:34:48 +0000 Subject: [PATCH] Fix search projects api requests missing scope permission Return an empty array if the user does not have permission to view the scope requested. Also a refactor to move scope permission check and search navigation into a library so it can be shared across the code Changelog: fixed --- app/helpers/projects_helper.rb | 47 --- app/helpers/search_helper.rb | 70 +--- app/services/search/project_service.rb | 23 +- app/services/search_service.rb | 10 - ee/app/helpers/ee/search_helper.rb | 12 +- ee/app/services/ee/search_service.rb | 1 - ee/lib/ee/search/navigation.rb | 20 + .../search/elastic/project_search_spec.rb | 11 +- ee/spec/helpers/search_helper_spec.rb | 71 +--- ee/spec/lib/search/navigation_spec.rb | 41 ++ lib/api/search.rb | 10 +- lib/search/navigation.rb | 158 ++++++++ .../features/snippets/search_snippets_spec.rb | 2 +- spec/helpers/search_helper_spec.rb | 363 ++---------------- spec/lib/search/navigation_spec.rb | 280 ++++++++++++++ spec/requests/api/search_spec.rb | 10 + 16 files changed, 586 insertions(+), 543 deletions(-) create mode 100644 ee/lib/ee/search/navigation.rb create mode 100644 ee/spec/lib/search/navigation_spec.rb create mode 100644 lib/search/navigation.rb create mode 100644 spec/lib/search/navigation_spec.rb diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index b7f15118b8d70..a7ea26b264b9a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -172,18 +172,6 @@ def visible_fork_source(project) project.fork_source if project.fork_source && can?(current_user, :read_project, project.fork_source) end - def project_search_tabs?(tab) - return false unless @project.present? - - abilities = Array(search_tab_ability_map[tab]) - - if @project.respond_to?(:each) # support multi-project select - @project.any? { |project| abilities.any? { |ability| can?(current_user, ability, project) } } - else - abilities.any? { |ability| can?(current_user, ability, @project) } - end - end - def can_change_visibility_level?(project, current_user) can?(current_user, :change_visibility_level, project) end @@ -614,41 +602,6 @@ def configure_oauth_import_message(provider, help_url) s_(str).html_safe % { provider: provider, link_start: link_start, link_end: '</a>'.html_safe } end - def tab_ability_map - { - cycle_analytics: :read_cycle_analytics, - environments: :read_environment, - metrics_dashboards: :metrics_dashboard, - milestones: :read_milestone, - snippets: :read_snippet, - settings: :admin_project, - builds: :read_build, - clusters: :read_cluster, - serverless: :read_cluster, - terraform: :read_terraform_state, - error_tracking: :read_sentry_issue, - alert_management: :read_alert_management_alert, - incidents: :read_issue, - labels: :read_label, - issues: :read_issue, - project_members: :read_project_member, - wiki: :read_wiki, - feature_flags: :read_feature_flag, - analytics: :read_analytics - } - end - - def search_tab_ability_map - @search_tab_ability_map ||= tab_ability_map.merge( - blobs: :read_code, - commits: :read_code, - merge_requests: :read_merge_request, - notes: [:read_merge_request, :read_code, :read_issue, :read_snippet], - users: :read_project_member, - wiki_blobs: :read_wiki - ) - end - def project_lfs_status(project) if project.lfs_enabled? content_tag(:span, class: 'lfs-enabled') do diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 1dd949630c277..cd32023adb630 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -464,65 +464,15 @@ def search_filter_link_json(scope, label, data, search) result end - def show_code_search_tab? - return true if project_search_tabs?(:blobs) - - @project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_code_tab) - end - - def show_wiki_search_tab? - return true if project_search_tabs?(:wiki_blobs) - - @project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_wiki_tab) - end - - def show_commits_search_tab? - return true if project_search_tabs?(:commits) - - @project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab) - end - - def show_issues_search_tab? - return true if project_search_tabs?(:issues) - - @project.nil? && feature_flag_tab_enabled?(:global_search_issues_tab) - end - - def show_merge_requests_search_tab? - return true if project_search_tabs?(:merge_requests) - - @project.nil? && feature_flag_tab_enabled?(:global_search_merge_requests_tab) - end - - def show_comments_search_tab? - return true if project_search_tabs?(:notes) - - @project.nil? && search_service.show_elasticsearch_tabs? - end - - def show_snippets_search_tab? - search_service.show_snippets? && @project.nil? && feature_flag_tab_enabled?(:global_search_snippet_titles_tab) - end - - # search page scope navigation - def search_navigation + def nav_options { - projects: { sort: 1, label: _("Projects"), data: { qa_selector: 'projects_tab' }, condition: @project.nil? }, - blobs: { sort: 2, label: _("Code"), data: { qa_selector: 'code_tab' }, condition: show_code_search_tab? }, - # sort: 3 is reserved for EE items - issues: { sort: 4, label: _("Issues"), condition: show_issues_search_tab? }, - merge_requests: { sort: 5, label: _("Merge requests"), condition: show_merge_requests_search_tab? }, - wiki_blobs: { sort: 6, label: _("Wiki"), condition: show_wiki_search_tab? }, - commits: { sort: 7, label: _("Commits"), condition: show_commits_search_tab? }, - notes: { sort: 8, label: _("Comments"), condition: show_comments_search_tab? }, - milestones: { sort: 9, label: _("Milestones"), condition: project_search_tabs?(:milestones) || @project.nil? }, - users: { sort: 10, label: _("Users"), condition: show_user_search_tab? }, - snippet_titles: { sort: 11, label: _("Snippets"), search: { snippets: true, group_id: nil, project_id: nil }, condition: show_snippets_search_tab? } + show_snippets: search_service.show_snippets? } end def search_navigation_json - sorted_navigation = search_navigation.sort_by { |_, h| h[:sort] } + search_navigation = Search::Navigation.new(user: current_user, project: @project, group: @group, options: nav_options) + sorted_navigation = search_navigation.tabs.sort_by { |_, h| h[:sort] } sorted_navigation.each_with_object({}) do |(key, value), hash| hash[key] = search_filter_link_json(key, value[:label], value[:data], value[:search]) if value[:condition] @@ -604,14 +554,6 @@ def highlight_and_truncate_issuable(issuable, search_term, _search_highlight) simple_search_highlight_and_truncate(issuable.description, search_term, highlighter: '<span class="gl-text-gray-900 gl-font-weight-bold">\1</span>') end - def show_user_search_tab? - return project_search_tabs?(:users) if @project - return false unless can?(current_user, :read_users_list) - return true if @group - - Feature.enabled?(:global_search_users_tab, current_user, type: :ops) - end - def issuable_state_to_badge_class(issuable) # Closed is considered "danger" for MR so we need to handle separately if issuable.is_a?(::MergeRequest) @@ -640,10 +582,6 @@ def issuable_state_text(issuable) end end - def feature_flag_tab_enabled?(flag) - @group.present? || Feature.enabled?(flag, current_user, type: :ops) - end - def sanitized_search_params sanitized_params = params.dup diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb index 71314f85984cb..73d46a9ba70c2 100644 --- a/app/services/search/project_service.rb +++ b/app/services/search/project_service.rb @@ -4,7 +4,6 @@ module Search class ProjectService include Search::Filter include Gitlab::Utils::StrongMemoize - include ProjectsHelper ALLOWED_SCOPES = %w(blobs issues merge_requests wiki_blobs commits notes milestones users).freeze @@ -18,13 +17,13 @@ def initialize(user, project_or_projects, params) def execute Gitlab::ProjectSearchResults.new(current_user, - params[:search], - project: project, - repository_ref: params[:repository_ref], - order_by: params[:order_by], - sort: params[:sort], - filters: filters - ) + params[:search], + project: project, + repository_ref: params[:repository_ref], + order_by: params[:order_by], + sort: params[:sort], + filters: filters + ) end def allowed_scopes @@ -33,10 +32,12 @@ def allowed_scopes def scope strong_memoize(:scope) do - next params[:scope] if allowed_scopes.include?(params[:scope]) && project_search_tabs?(params[:scope].to_sym) + search_navigation = Search::Navigation.new(user: current_user, project: project) + scope = params[:scope] + next scope if allowed_scopes.include?(scope) && search_navigation.tab_enabled_for_project?(scope.to_sym) - allowed_scopes.find do |scope| - project_search_tabs?(scope.to_sym) + allowed_scopes.find do |s| + search_navigation.tab_enabled_for_project?(s.to_sym) end end end diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 5705e4c7cef0e..433e9b0da6d35 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -102,16 +102,6 @@ def level end end - def show_elasticsearch_tabs? - # overridden in EE - false - end - - def show_epics? - # overridden in EE - false - end - def global_search_enabled_for_scope? return false if show_snippets? && Feature.disabled?(:global_search_snippet_titles_tab, current_user, type: :ops) diff --git a/ee/app/helpers/ee/search_helper.rb b/ee/app/helpers/ee/search_helper.rb index 13721f6c706ca..c3fd6d226329e 100644 --- a/ee/app/helpers/ee/search_helper.rb +++ b/ee/app/helpers/ee/search_helper.rb @@ -144,11 +144,6 @@ def search_sort_options options + original_options end - override :search_navigation - def search_navigation - super.merge(epics: { sort: 3, label: _("Epics"), condition: @project.nil? && search_service.show_epics? }) - end - override :search_scope def search_scope if current_controller?(:epics) @@ -206,5 +201,12 @@ def gitlab_com_snippet_db_search? ::Gitlab.com? && ::Gitlab::CurrentSettings.search_using_elasticsearch?(scope: nil) end + + override :nav_options + def nav_options + super.merge(show_epics: search_service.show_epics?, + show_elasticsearch_tabs: search_service.show_elasticsearch_tabs? + ) + end end end diff --git a/ee/app/services/ee/search_service.rb b/ee/app/services/ee/search_service.rb index 2e2c53267ed68..40a4fe532376f 100644 --- a/ee/app/services/ee/search_service.rb +++ b/ee/app/services/ee/search_service.rb @@ -17,7 +17,6 @@ def show_epics? search_service.allowed_scopes.include?('epics') end - override :show_elasticsearch_tabs? def show_elasticsearch_tabs? ::Gitlab::CurrentSettings.search_using_elasticsearch?(scope: search_service.elasticsearchable_scope) end diff --git a/ee/lib/ee/search/navigation.rb b/ee/lib/ee/search/navigation.rb new file mode 100644 index 0000000000000..8971191502e9d --- /dev/null +++ b/ee/lib/ee/search/navigation.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module EE + module Search + module Navigation + extend ::Gitlab::Utils::Override + + override :tabs + def tabs + super.merge(epics: { sort: 3, label: _("Epics"), condition: show_epics_search_tab? }) + end + + private + + def show_epics_search_tab? + project.nil? && !!options[:show_epics] + end + end + end +end diff --git a/ee/spec/features/search/elastic/project_search_spec.rb b/ee/spec/features/search/elastic/project_search_spec.rb index 3604a3d33992d..d3685fc1f99d7 100644 --- a/ee/spec/features/search/elastic/project_search_spec.rb +++ b/ee/spec/features/search/elastic/project_search_spec.rb @@ -127,7 +127,15 @@ describe 'renders error when zoekt search fails' do let(:query) { 'test' } - let(:search_service) { instance_double(SearchService, scope: 'blobs', use_elasticsearch?: true, use_zoekt?: true) } + let(:search_service) do + instance_double(Search::ProjectService, + scope: 'blobs', + use_elasticsearch?: true, + use_zoekt?: true, + elasticsearchable_scope: project + ) + end + let(:results) { Gitlab::Zoekt::SearchResults.new(user, query) } before do @@ -135,6 +143,7 @@ allow_next_instance_of(SearchService) do |service| allow(service).to receive(:search_service).and_return(search_service) + allow(service).to receive(:show_epics?).and_return(false) allow(service).to receive(:search_results).and_return(results) allow(results).to receive(:zoekt_search).and_return({ Error: 'failed to parse query' }) end diff --git a/ee/spec/helpers/search_helper_spec.rb b/ee/spec/helpers/search_helper_spec.rb index aec7c4bf66c0f..de302fdc9fe1b 100644 --- a/ee/spec/helpers/search_helper_spec.rb +++ b/ee/spec/helpers/search_helper_spec.rb @@ -327,53 +327,25 @@ end end - describe '.search_navigation' do - using RSpec::Parameterized::TableSyntax - let(:user) { build(:user) } - let(:project) { build(:project) } - - before do - allow(self).to receive(:current_user).and_return(user) - allow(self).to receive(:can?).and_return(true) - end - - context 'for epics' do - where(:global_project, :show_epics, :condition) do - nil | false | false - ref(:project) | true | false - ref(:project) | false | false - nil | true | true + describe '.search_navigation_json' do + context 'when all options enabled' do + before do + allow(self).to receive(:current_user).and_return(build(:user)) + allow(self).to receive(:can?).and_return(true) + allow(self).to receive(:project_search_tabs?).and_return(true) + allow(self).to receive(:feature_flag_tab_enabled?).and_return(true) + allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(true) + allow(search_service).to receive(:show_epics?).and_return(true) + allow(search_service).to receive(:show_snippets?).and_return(true) + @project = nil end - with_them do - it 'data item condition is set correctly' do - @project = global_project - allow(search_service).to receive(:show_epics?).and_return(show_epics) - - expect(search_navigation[:epics][:condition]).to eq(condition) - end + it 'returns items in order' do + expect(Gitlab::Json.parse(search_navigation_json).keys).to eq(%w[projects blobs epics issues merge_requests wiki_blobs commits notes milestones users snippet_titles]) end end end - describe '.search_navigation_json with .search_navigation' do - before do - allow(self).to receive(:current_user).and_return(build(:user)) - allow(self).to receive(:can?).and_return(true) - allow(self).to receive(:project_search_tabs?).and_return(true) - allow(self).to receive(:feature_flag_tab_enabled?).and_return(true) - allow(self).to receive(:feature_flag_tab_enabled?).and_return(true) - allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(true) - allow(search_service).to receive(:show_epics?).and_return(true) - allow(search_service).to receive(:show_snippets?).and_return(true) - @project = nil - end - - it 'test search navigation item order for CE all options enabled' do - expect(Gitlab::Json.parse(search_navigation_json).keys).to eq(%w[projects blobs epics issues merge_requests wiki_blobs commits notes milestones users snippet_titles]) - end - end - describe '.search_filter_link_json' do using RSpec::Parameterized::TableSyntax @@ -415,23 +387,6 @@ end end - describe 'show_elasticsearch_tabs' do - let(:user) { build(:user) } - let_it_be(:project) { build(:project) } - - before do - stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true) - allow(self).to receive(:current_user).and_return(user) - allow(self).to receive(:can?).and_return(false) - allow(self).to receive(:project_search_tabs?).and_return(false) - allow(self).to receive(:feature_flag_tab_enabled?).and_return(false) - end - - it 'show_elasticsearch_tabs? returns true' do - expect(search_service.show_elasticsearch_tabs?).to eq(true) - end - end - describe '#wiki_blob_link' do let_it_be(:group) { create(:group, :wiki_repo) } let(:wiki_blob) do diff --git a/ee/spec/lib/search/navigation_spec.rb b/ee/spec/lib/search/navigation_spec.rb new file mode 100644 index 0000000000000..3b7b816607748 --- /dev/null +++ b/ee/spec/lib/search/navigation_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Search::Navigation, feature_category: :global_search do + describe '#tabs' do + using RSpec::Parameterized::TableSyntax + + let(:user) { instance_double(User) } + let(:project_double) { instance_double(Project) } + let(:options) { {} } + let(:search_navigation) { described_class.new(user: user, project: project, options: options) } + + before do + allow(search_navigation).to receive(:can?).and_return(true) + allow(search_navigation).to receive(:tab_enabled_for_project?).and_return(false) + allow(search_navigation).to receive(:feature_flag_tab_enabled?).and_return(false) + end + + subject(:tabs) { search_navigation.tabs } + + context 'for epics tab' do + where(:project, :show_epics, :condition) do + nil | false | false + nil | nil | false + ref(:project_double) | true | false + ref(:project_double) | false | false + ref(:project_double) | nil | false + nil | true | true + end + + with_them do + let(:options) { { show_epics: show_epics } } + + it 'data item condition is set correctly' do + expect(tabs[:epics][:condition]).to eq(condition) + end + end + end + end +end diff --git a/lib/api/search.rb b/lib/api/search.rb index 954c3cd9f9ef0..b14fce13f5e2d 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -59,6 +59,8 @@ def search_service(additional_params = {}) end def search(additional_params = {}) + return Kaminari.paginate_array([]) if @project.present? && !project_scope_allowed? + search_service = search_service(additional_params) if search_service.global_search? && !search_service.global_search_enabled_for_scope? forbidden!('Global Search is disabled for this scope') @@ -95,6 +97,10 @@ def search(additional_params = {}) ) end + def project_scope_allowed? + ::Search::Navigation.new(user: current_user, project: @project).tab_enabled_for_project?(params[:scope].to_sym) + end + def snippets? %w(snippet_titles).include?(params[:scope]).to_s end @@ -108,9 +114,7 @@ def preload_method end def verify_search_scope!(resource:) - # In EE we have additional validation requirements for searches. - # Defining this method here as a noop allows us to easily extend it in - # EE, without having to modify this file directly. + # no-op end def search_type(additional_params = {}) diff --git a/lib/search/navigation.rb b/lib/search/navigation.rb new file mode 100644 index 0000000000000..3594ac0dc306c --- /dev/null +++ b/lib/search/navigation.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +module Search + class Navigation + include Gitlab::Allowable + + def initialize(user:, project: nil, group: nil, options: {}) + @user = user + @project = project + @group = group + @options = options + end + + def tab_enabled_for_project?(tab) + return false unless project.present? + + abilities = Array(search_tab_ability_map[tab]) + Array.wrap(project).any? { |p| abilities.any? { |ability| can?(user, ability, p) } } + end + + def tabs + { + projects: { + sort: 1, + label: _("Projects"), + data: { qa_selector: 'projects_tab' }, + condition: project.nil? + }, + blobs: { + sort: 2, + label: _("Code"), + data: { qa_selector: 'code_tab' }, + condition: show_code_search_tab? + }, + # sort: 3 is reserved for EE items + issues: { + sort: 4, + label: _("Issues"), + condition: show_issues_search_tab? + }, + merge_requests: { + sort: 5, + label: _("Merge requests"), + condition: show_merge_requests_search_tab? + }, + wiki_blobs: { + sort: 6, + label: _("Wiki"), + condition: show_wiki_search_tab? + }, + commits: { + sort: 7, + label: _("Commits"), + condition: show_commits_search_tab? + }, + notes: { + sort: 8, + label: _("Comments"), + condition: show_comments_search_tab? + }, + milestones: { + sort: 9, label: _("Milestones"), + condition: show_milestones_search_tab? + }, + users: { + sort: 10, + label: _("Users"), + condition: show_user_search_tab? + }, + snippet_titles: { + sort: 11, + label: _("Snippets"), + search: { snippets: true, group_id: nil, project_id: nil }, + condition: show_snippets_search_tab? + } + } + end + + private + + attr_reader :user, :project, :group, :options + + def show_elasticsearch_tabs? + !!options[:show_elasticsearch_tabs] + end + + def search_tab_ability_map + { + milestones: :read_milestone, + snippets: :read_snippet, + issues: :read_issue, + blobs: :read_code, + commits: :read_code, + merge_requests: :read_merge_request, + notes: [:read_merge_request, :read_code, :read_issue, :read_snippet], + users: :read_project_member, + wiki_blobs: :read_wiki + } + end + + def show_user_search_tab? + return true if tab_enabled_for_project?(:users) + return false unless can?(user, :read_users_list) + + project.nil? && feature_flag_tab_enabled?(:global_search_users_tab) + end + + def show_code_search_tab? + return true if tab_enabled_for_project?(:blobs) + + project.nil? && show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_code_tab) + end + + def show_wiki_search_tab? + return true if tab_enabled_for_project?(:wiki_blobs) + + project.nil? && show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_wiki_tab) + end + + def show_commits_search_tab? + return true if tab_enabled_for_project?(:commits) + + project.nil? && show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab) + end + + def show_issues_search_tab? + return true if tab_enabled_for_project?(:issues) + + project.nil? && feature_flag_tab_enabled?(:global_search_issues_tab) + end + + def show_merge_requests_search_tab? + return true if tab_enabled_for_project?(:merge_requests) + + project.nil? && feature_flag_tab_enabled?(:global_search_merge_requests_tab) + end + + def show_comments_search_tab? + return true if tab_enabled_for_project?(:notes) + + project.nil? && show_elasticsearch_tabs? + end + + def show_snippets_search_tab? + !!options[:show_snippets] && project.nil? && feature_flag_tab_enabled?(:global_search_snippet_titles_tab) + end + + def show_milestones_search_tab? + project.nil? || tab_enabled_for_project?(:milestones) + end + + def feature_flag_tab_enabled?(flag) + group.present? || Feature.enabled?(flag, user, type: :ops) + end + end +end + +Search::Navigation.prepend_mod diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb index 66c4041241ce7..afb53c563de96 100644 --- a/spec/features/snippets/search_snippets_spec.rb +++ b/spec/features/snippets/search_snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Search Snippets', :js, feature_category: :source_code_management do +RSpec.describe 'Search Snippets', :js, feature_category: :global_search do it 'user searches for snippets by title' do public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle') private_snippet = create(:personal_snippet, :private, title: 'Middle and End') diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index a71a8392e1c1f..1ff7e48abfc25 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -722,78 +722,6 @@ def simple_sanitize(str) end end - describe '#show_user_search_tab?' do - subject { show_user_search_tab? } - - let(:current_user) { build(:user) } - - before do - allow(self).to receive(:current_user).and_return(current_user) - end - - context 'when project search' do - before do - @project = :some_project - - expect(self).to receive(:project_search_tabs?) - .with(:users) - .and_return(:value) - end - - it 'delegates to project_search_tabs?' do - expect(subject).to eq(:value) - end - end - - context 'when group search' do - before do - @group = :some_group - end - - context 'when current_user can read_users_list' do - before do - allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(true) - end - - it { is_expected.to eq(true) } - end - - context 'when current_user cannot read_users_list' do - before do - allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(false) - end - - it { is_expected.to eq(false) } - end - end - - context 'when global search' do - context 'when current_user can read_users_list' do - before do - allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(true) - end - - it { is_expected.to eq(true) } - - context 'when global_search_user_tab feature flag is disabled' do - before do - stub_feature_flags(global_search_users_tab: false) - end - - it { is_expected.to eq(false) } - end - end - - context 'when current_user cannot read_users_list' do - before do - allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(false) - end - - it { is_expected.to eq(false) } - end - end - end - describe '#repository_ref' do using RSpec::Parameterized::TableSyntax @@ -1113,248 +1041,24 @@ def simple_sanitize(str) end end - describe '.search_navigation' do - using RSpec::Parameterized::TableSyntax - let(:user) { build(:user) } - let_it_be(:project) { build(:project) } - - before do - allow(self).to receive(:current_user).and_return(user) - allow(self).to receive(:can?).and_return(true) - allow(self).to receive(:project_search_tabs?).and_return(false) - allow(self).to receive(:feature_flag_tab_enabled?).and_return(false) - end - - context 'projects' do - where(:global_project, :condition) do - nil | true - ref(:project) | false - end - - with_them do - it 'data item condition is set correctly' do - @project = global_project - - expect(search_navigation[:projects][:condition]).to eq(condition) - end - end - end - - context 'code' do - where(:feature_flag_tab_enabled, :show_elasticsearch_tabs, :global_project, :project_search_tabs, :condition) do - false | false | nil | false | false - true | true | nil | true | true - true | false | nil | false | false - false | true | nil | false | false - false | false | ref(:project) | true | true - true | false | ref(:project) | false | false - end - - with_them do - it 'data item condition is set correctly' do - @project = global_project - allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs) - allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_code_tab).and_return(feature_flag_tab_enabled) - allow(self).to receive(:project_search_tabs?).with(:blobs).and_return(project_search_tabs) - - expect(search_navigation[:blobs][:condition]).to eq(condition) - end - end - end - - context 'issues' do - where(:project_search_tabs, :global_search_issues_tab, :global_project, :condition) do - false | false | nil | false - false | true | nil | true - false | true | ref(:project) | false - false | false | ref(:project) | false - true | false | nil | true - true | true | nil | true - true | false | ref(:project) | true - true | true | ref(:project) | true - end - - with_them do - it 'data item condition is set correctly' do - @project = global_project - allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_issues_tab).and_return(global_search_issues_tab) - allow(self).to receive(:project_search_tabs?).with(:issues).and_return(project_search_tabs) - - expect(search_navigation[:issues][:condition]).to eq(condition) - end - end - end - - context 'merge requests' do - where(:project_search_tabs, :feature_flag_tab_enabled, :global_project, :condition) do - false | false | nil | false - true | false | nil | true - false | false | ref(:project) | false - true | false | ref(:project) | true - false | true | nil | true - true | true | nil | true - false | true | ref(:project) | false - true | true | ref(:project) | true - end - - with_them do - it 'data item condition is set correctly' do - @project = global_project - allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_merge_requests_tab).and_return(feature_flag_tab_enabled) - allow(self).to receive(:project_search_tabs?).with(:merge_requests).and_return(project_search_tabs) - - expect(search_navigation[:merge_requests][:condition]).to eq(condition) - end - end - end - - context 'wiki' do - where(:global_search_wiki_tab, :show_elasticsearch_tabs, :global_project, :project_search_tabs, :condition) do - false | false | nil | true | true - false | false | nil | false | false - false | false | ref(:project) | false | false - false | true | nil | false | false - false | true | ref(:project) | false | false - true | false | nil | false | false - true | true | ref(:project) | false | false - end - - with_them do - it 'data item condition is set correctly' do - @project = global_project - allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs) - allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_wiki_tab).and_return(global_search_wiki_tab) - allow(self).to receive(:project_search_tabs?).with(:wiki_blobs).and_return(project_search_tabs) - - expect(search_navigation[:wiki_blobs][:condition]).to eq(condition) - end - end - end - - context 'commits' do - where(:global_search_commits_tab, :show_elasticsearch_tabs, :global_project, :project_search_tabs, :condition) do - false | false | nil | true | true - false | false | ref(:project) | true | true - false | false | nil | false | false - false | true | ref(:project) | false | false - false | true | nil | false | false - true | false | nil | false | false - true | false | ref(:project) | false | false - true | true | ref(:project) | false | false - true | true | nil | false | true - end - - with_them do - it 'data item condition is set correctly' do - @project = global_project - allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs) - allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_commits_tab).and_return(global_search_commits_tab) - allow(self).to receive(:project_search_tabs?).with(:commits).and_return(project_search_tabs) - - expect(search_navigation[:commits][:condition]).to eq(condition) - end - end - end - - context 'comments' do - where(:project_search_tabs, :show_elasticsearch_tabs, :global_project, :condition) do - true | true | nil | true - true | true | ref(:project) | true - false | false | nil | false - false | false | ref(:project) | false - false | true | nil | true - false | true | ref(:project) | false - true | false | nil | true - true | false | ref(:project) | true - end - - with_them do - it 'data item condition is set correctly' do - @project = global_project - allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs) - allow(self).to receive(:project_search_tabs?).with(:notes).and_return(project_search_tabs) - - expect(search_navigation[:notes][:condition]).to eq(condition) - end - end - end - - context 'milestones' do - where(:global_project, :project_search_tabs, :condition) do - ref(:project) | true | true - nil | false | true - ref(:project) | false | false - nil | true | true - end - - with_them do - it 'data item condition is set correctly' do - @project = global_project - allow(self).to receive(:project_search_tabs?).with(:milestones).and_return(project_search_tabs) - - expect(search_navigation[:milestones][:condition]).to eq(condition) - end - end - end - - context 'users' do - where(:show_user_search_tab, :condition) do - true | true - false | false - end - - with_them do - it 'data item condition is set correctly' do - allow(self).to receive(:show_user_search_tab?).and_return(show_user_search_tab) - - expect(search_navigation[:users][:condition]).to eq(condition) - end - end - end - - context 'snippet_titles' do - where(:global_project, :global_show_snippets, :global_feature_flag_enabled, :condition) do - ref(:project) | true | false | false - nil | false | false | false - ref(:project) | false | false | false - nil | true | false | false - ref(:project) | true | true | false - nil | false | true | false - ref(:project) | false | true | false - nil | true | true | true - end - - with_them do - it 'data item condition is set correctly' do - allow(search_service).to receive(:show_snippets?).and_return(global_show_snippets) - allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_snippet_titles_tab) - .and_return(global_feature_flag_enabled) - @project = global_project - - expect(search_navigation[:snippet_titles][:condition]).to eq(condition) - end - end - end - end - describe '.search_navigation_json' do using RSpec::Parameterized::TableSyntax - context 'with data' do + context 'with some tab conditions set to false' do example_data_1 = { projects: { label: _("Projects"), condition: true }, - blobs: { label: _("Code"), condition: false } + blobs: { label: _("Code"), condition: false } } example_data_2 = { projects: { label: _("Projects"), condition: false }, - blobs: { label: _("Code"), condition: false } + blobs: { label: _("Code"), condition: false } } example_data_3 = { projects: { label: _("Projects"), condition: true }, - blobs: { label: _("Code"), condition: true }, - epics: { label: _("Epics"), condition: true } + blobs: { label: _("Code"), condition: true }, + epics: { label: _("Epics"), condition: true } } where(:data, :matcher) do @@ -1365,28 +1069,31 @@ def simple_sanitize(str) with_them do it 'renders data correctly' do - allow(self).to receive(:search_navigation).with(no_args).and_return(data) + allow(self).to receive(:current_user).and_return(build(:user)) + allow_next_instance_of(Search::Navigation) do |search_nav| + allow(search_nav).to receive(:tabs).and_return(data) + end expect(search_navigation_json).to instance_exec(&matcher) end end end - end - describe '.search_navigation_json with .search_navigation' do - before do - allow(self).to receive(:current_user).and_return(build(:user)) - allow(self).to receive(:can?).and_return(true) - allow(self).to receive(:project_search_tabs?).and_return(true) - allow(self).to receive(:feature_flag_tab_enabled?).and_return(true) - allow(self).to receive(:feature_flag_tab_enabled?).and_return(true) - allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(true) - allow(search_service).to receive(:show_snippets?).and_return(true) - @project = nil - end + context 'when all options enabled' do + before do + allow(self).to receive(:current_user).and_return(build(:user)) + allow(search_service).to receive(:show_snippets?).and_return(true) + allow_next_instance_of(Search::Navigation) do |search_nav| + allow(search_nav).to receive(:tab_enabled_for_project?).and_return(true) + allow(search_nav).to receive(:feature_flag_tab_enabled?).and_return(true) + end - it 'test search navigation item order for CE all options enabled' do - expect(Gitlab::Json.parse(search_navigation_json).keys).to eq(%w[projects blobs issues merge_requests wiki_blobs commits notes milestones users snippet_titles]) + @project = nil + end + + it 'returns items in order' do + expect(Gitlab::Json.parse(search_navigation_json).keys).to eq(%w[projects blobs issues merge_requests wiki_blobs commits notes milestones users snippet_titles]) + end end end @@ -1431,30 +1138,6 @@ def simple_sanitize(str) end end - describe 'show_elasticsearch_tabs' do - subject { search_service.show_elasticsearch_tabs? } - - let(:user) { build(:user) } - - before do - allow(self).to receive(:current_user).and_return(user) - end - - it { is_expected.to eq(false) } - end - - describe 'show_epics' do - subject { search_service.show_epics? } - - let(:user) { build(:user) } - - before do - allow(self).to receive(:current_user).and_return(user) - end - - it { is_expected.to eq(false) } - end - describe '#wiki_blob_link' do let_it_be(:project) { create :project, :wiki_repo } let(:wiki_blob) do diff --git a/spec/lib/search/navigation_spec.rb b/spec/lib/search/navigation_spec.rb new file mode 100644 index 0000000000000..da8c27b499023 --- /dev/null +++ b/spec/lib/search/navigation_spec.rb @@ -0,0 +1,280 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Search::Navigation, feature_category: :global_search do + let(:user) { instance_double(User) } + let(:project_double) { instance_double(Project) } + let(:options) { {} } + let(:search_navigation) { described_class.new(user: user, project: project, options: options) } + + describe '#tab_enabled_for_project?' do + let(:project) { project_double } + let(:tab) { :blobs } + + subject(:tab_enabled_for_project) { search_navigation.tab_enabled_for_project?(tab) } + + context 'when user has ability for tab' do + before do + allow(search_navigation).to receive(:can?).with(user, :read_code, project_double).and_return(true) + end + + it { is_expected.to eq(true) } + end + + context 'when user does not have ability for tab' do + before do + allow(search_navigation).to receive(:can?).with(user, :read_code, project_double).and_return(false) + end + + it { is_expected.to eq(false) } + end + + context 'when an array of projects is provided' do + let(:project) { Array.wrap(project_double) } + + before do + allow(search_navigation).to receive(:can?).with(user, :read_code, project_double).and_return(true) + end + + it { is_expected.to eq(true) } + end + + context 'when project is not present' do + let_it_be(:project) { nil } + + it { is_expected.to eq(false) } + end + end + + describe '#tabs' do + using RSpec::Parameterized::TableSyntax + + before do + allow(search_navigation).to receive(:can?).and_return(true) + allow(search_navigation).to receive(:tab_enabled_for_project?).and_return(false) + allow(search_navigation).to receive(:feature_flag_tab_enabled?).and_return(false) + end + + subject(:tabs) { search_navigation.tabs } + + context 'for projects tab' do + where(:project, :condition) do + nil | true + ref(:project_double) | false + end + + with_them do + it 'data item condition is set correctly' do + expect(tabs[:projects][:condition]).to eq(condition) + end + end + end + + context 'for code tab' do + where(:feature_flag_enabled, :show_elasticsearch_tabs, :project, :tab_enabled, :condition) do + false | false | nil | false | false + true | true | nil | true | true + true | false | nil | false | false + false | true | nil | false | false + false | false | ref(:project_double) | true | true + true | false | ref(:project_double) | false | false + end + + with_them do + let(:options) { { show_elasticsearch_tabs: show_elasticsearch_tabs } } + + it 'data item condition is set correctly' do + allow(search_navigation).to receive(:feature_flag_tab_enabled?) + .with(:global_search_code_tab).and_return(feature_flag_enabled) + allow(search_navigation).to receive(:tab_enabled_for_project?).with(:blobs).and_return(tab_enabled) + + expect(tabs[:blobs][:condition]).to eq(condition) + end + end + end + + context 'for issues tab' do + where(:tab_enabled, :feature_flag_enabled, :project, :condition) do + false | false | nil | false + false | true | nil | true + false | true | ref(:project_double) | false + false | false | ref(:project_double) | false + true | false | nil | true + true | true | nil | true + true | false | ref(:project_double) | true + true | true | ref(:project_double) | true + end + + with_them do + it 'data item condition is set correctly' do + allow(search_navigation).to receive(:feature_flag_tab_enabled?) + .with(:global_search_issues_tab).and_return(feature_flag_enabled) + allow(search_navigation).to receive(:tab_enabled_for_project?).with(:issues).and_return(tab_enabled) + + expect(tabs[:issues][:condition]).to eq(condition) + end + end + end + + context 'for merge requests tab' do + where(:tab_enabled, :feature_flag_enabled, :project, :condition) do + false | false | nil | false + true | false | nil | true + false | false | ref(:project_double) | false + true | false | ref(:project_double) | true + false | true | nil | true + true | true | nil | true + false | true | ref(:project_double) | false + true | true | ref(:project_double) | true + end + + with_them do + it 'data item condition is set correctly' do + allow(search_navigation).to receive(:feature_flag_tab_enabled?) + .with(:global_search_merge_requests_tab).and_return(feature_flag_enabled) + allow(search_navigation).to receive(:tab_enabled_for_project?).with(:merge_requests).and_return(tab_enabled) + + expect(tabs[:merge_requests][:condition]).to eq(condition) + end + end + end + + context 'for wiki tab' do + where(:feature_flag_enabled, :show_elasticsearch_tabs, :project, :tab_enabled, :condition) do + false | false | nil | true | true + false | false | nil | false | false + false | false | ref(:project_double) | false | false + false | true | nil | false | false + false | true | ref(:project_double) | false | false + true | false | nil | false | false + true | true | ref(:project_double) | false | false + end + + with_them do + let(:options) { { show_elasticsearch_tabs: show_elasticsearch_tabs } } + + it 'data item condition is set correctly' do + allow(search_navigation).to receive(:feature_flag_tab_enabled?) + .with(:global_search_wiki_tab).and_return(feature_flag_enabled) + allow(search_navigation).to receive(:tab_enabled_for_project?).with(:wiki_blobs).and_return(tab_enabled) + + expect(tabs[:wiki_blobs][:condition]).to eq(condition) + end + end + end + + context 'for commits tab' do + where(:feature_flag_enabled, :show_elasticsearch_tabs, :project, :tab_enabled, :condition) do + false | false | nil | true | true + false | false | ref(:project_double) | true | true + false | false | nil | false | false + false | true | ref(:project_double) | false | false + false | true | nil | false | false + true | false | nil | false | false + true | false | ref(:project_double) | false | false + true | true | ref(:project_double) | false | false + true | true | nil | false | true + end + + with_them do + let(:options) { { show_elasticsearch_tabs: show_elasticsearch_tabs } } + + it 'data item condition is set correctly' do + allow(search_navigation).to receive(:feature_flag_tab_enabled?) + .with(:global_search_commits_tab).and_return(feature_flag_enabled) + allow(search_navigation).to receive(:tab_enabled_for_project?).with(:commits).and_return(tab_enabled) + + expect(tabs[:commits][:condition]).to eq(condition) + end + end + end + + context 'for comments tab' do + where(:tab_enabled, :show_elasticsearch_tabs, :project, :condition) do + true | true | nil | true + true | true | ref(:project_double) | true + false | false | nil | false + false | false | ref(:project_double) | false + false | true | nil | true + false | true | ref(:project_double) | false + true | false | nil | true + true | false | ref(:project_double) | true + end + + with_them do + let(:options) { { show_elasticsearch_tabs: show_elasticsearch_tabs } } + + it 'data item condition is set correctly' do + allow(search_navigation).to receive(:tab_enabled_for_project?).with(:notes).and_return(tab_enabled) + + expect(tabs[:notes][:condition]).to eq(condition) + end + end + end + + context 'for milestones tab' do + where(:project, :tab_enabled, :condition) do + ref(:project_double) | true | true + nil | false | true + ref(:project_double) | false | false + nil | true | true + end + + with_them do + it 'data item condition is set correctly' do + allow(search_navigation).to receive(:tab_enabled_for_project?).with(:milestones).and_return(tab_enabled) + + expect(tabs[:milestones][:condition]).to eq(condition) + end + end + end + + context 'for users tab' do + where(:feature_flag_enabled, :can_read_users_list, :project, :tab_enabled, :condition) do + false | false | ref(:project_double) | true | true + false | false | nil | false | false + false | true | nil | false | false + false | true | ref(:project_double) | false | false + true | true | nil | false | true + true | true | ref(:project_double) | false | false + end + + with_them do + it 'data item condition is set correctly' do + allow(search_navigation).to receive(:tab_enabled_for_project?).with(:users).and_return(tab_enabled) + allow(search_navigation).to receive(:can?) + .with(user, :read_users_list, project_double).and_return(can_read_users_list) + allow(search_navigation).to receive(:feature_flag_tab_enabled?) + .with(:global_search_users_tab).and_return(feature_flag_enabled) + + expect(tabs[:users][:condition]).to eq(condition) + end + end + end + + context 'for snippet_titles tab' do + where(:project, :show_snippets, :feature_flag_enabled, :condition) do + ref(:project_double) | true | false | false + nil | false | false | false + ref(:project_double) | false | false | false + nil | true | false | false + ref(:project_double) | true | true | false + nil | false | true | false + ref(:project_double) | false | true | false + nil | true | true | true + end + + with_them do + let(:options) { { show_snippets: show_snippets } } + + it 'data item condition is set correctly' do + allow(search_navigation).to receive(:feature_flag_tab_enabled?) + .with(:global_search_snippet_titles_tab).and_return(feature_flag_enabled) + + expect(tabs[:snippet_titles][:condition]).to eq(condition) + end + end + end + end +end diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index 1b331e9c0992d..0f0ce6ae61e5a 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -688,6 +688,16 @@ def request end end + context 'when user does not have permissions for scope' do + it 'returns an empty array' do + project.project_feature.update!(issues_access_level: Gitlab::VisibilityLevel::PRIVATE) + + get api(endpoint, user), params: { scope: 'issues', search: 'awesome' } + + expect(json_response).to be_empty + end + end + context 'when project does not exist' do it 'returns 404 error' do get api('/projects/0/search', user), params: { scope: 'issues', search: 'awesome' } -- GitLab