diff options
Diffstat (limited to 'spec/support/shared_examples/lib')
12 files changed, 498 insertions, 8 deletions
diff --git a/spec/support/shared_examples/lib/banzai/reference_parser_shared_examples.rb b/spec/support/shared_examples/lib/banzai/reference_parser_shared_examples.rb index d903c0f10e0..479b26977e2 100644 --- a/spec/support/shared_examples/lib/banzai/reference_parser_shared_examples.rb +++ b/spec/support/shared_examples/lib/banzai/reference_parser_shared_examples.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples "referenced feature visibility" do |*related_features| + let(:enable_user?) { false } let(:feature_fields) do related_features.map { |feature| (feature + "_access_level").to_sym } end @@ -35,8 +36,11 @@ RSpec.shared_examples "referenced feature visibility" do |*related_features| end context "when feature is enabled" do - # The project is public + # Allows implementing specs to enable finer-tuned permissions + let(:enable_user?) { true } + it "creates reference" do + # The project is public set_features_fields_to(ProjectFeature::ENABLED) expect(subject.nodes_visible_to_user(user, [link])).to eq([link]) diff --git a/spec/support/shared_examples/lib/gitlab/alert_management/payload.rb b/spec/support/shared_examples/lib/gitlab/alert_management/payload.rb new file mode 100644 index 00000000000..54b021e8371 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/alert_management/payload.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'parsable alert payload field with fallback' do |fallback, *paths| + context 'without payload' do + it { is_expected.to eq(fallback) } + end + + paths.each do |path| + context "with #{path}" do + let(:value) { 'some value' } + + before do + section, name = path.split('/') + raw_payload[section] = name ? { name => value } : value + end + + it { is_expected.to eq(value) } + end + end +end + +RSpec.shared_examples 'parsable alert payload field' do |*paths| + it_behaves_like 'parsable alert payload field with fallback', nil, *paths +end + +RSpec.shared_examples 'subclass has expected api' do + it 'defines all public methods in the base class' do + default_methods = Gitlab::AlertManagement::Payload::Base.public_instance_methods + subclass_methods = described_class.public_instance_methods + missing_methods = subclass_methods - default_methods + + expect(missing_methods).to be_empty + end +end diff --git a/spec/support/shared_examples/lib/gitlab/auth/atlassian_identity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/auth/atlassian_identity_shared_examples.rb new file mode 100644 index 00000000000..18a5087da3b --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/auth/atlassian_identity_shared_examples.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'an atlassian identity' do + it 'sets the proper values' do + expect(identity.extern_uid).to eq(extern_uid) + expect(identity.token).to eq(credentials[:token]) + expect(identity.refresh_token).to eq(credentials[:refresh_token]) + expect(identity.expires_at.to_i).to eq(credentials[:expires_at]) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb index 8cf6babe146..e93077c42e1 100644 --- a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb @@ -63,7 +63,7 @@ RSpec.shared_examples 'schedules resource mentions migration' do |resource_class it 'schedules background migrations' do Sidekiq::Testing.fake! do - Timecop.freeze do + freeze_time do resource_count = is_for_notes ? Note.count : resource_class.count expect(resource_count).to eq 5 diff --git a/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb index a3800f050bb..f018ece0d46 100644 --- a/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/kubernetes/network_policy_common_shared_examples.rb @@ -5,18 +5,19 @@ RSpec.shared_examples 'network policy common specs' do let(:namespace) { 'example-namespace' } let(:labels) { nil } + describe '#generate' do + subject { policy.generate } + + it { is_expected.to eq(Kubeclient::Resource.new(policy.resource)) } + end + describe 'as_json' do let(:json_policy) do { name: name, namespace: namespace, creation_timestamp: nil, - manifest: YAML.dump( - { - metadata: metadata, - spec: spec - }.deep_stringify_keys - ), + manifest: YAML.dump(policy.resource.deep_stringify_keys), is_autodevops: false, is_enabled: true } diff --git a/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb new file mode 100644 index 00000000000..6327367fcc2 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'handling all upload parameters conditions' do + context 'one root parameter' do + include_context 'with one temporary file for multipart' + + let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) } + let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename, remote_id: remote_id) } + + it 'builds an UploadedFile' do + expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file)) + + subject + end + end + + context 'two root parameters' do + include_context 'with two temporary files for multipart' + + let(:rewritten_fields) { rewritten_fields_hash('file1' => uploaded_filepath, 'file2' => uploaded_filepath2) } + let(:params) do + upload_parameters_for(filepath: uploaded_filepath, key: 'file1', filename: filename, remote_id: remote_id).merge( + upload_parameters_for(filepath: uploaded_filepath2, key: 'file2', filename: filename2, remote_id: remote_id2) + ) + end + + it 'builds UploadedFiles' do + expect_uploaded_files([ + { filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file1) }, + { filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(file2) } + ]) + + subject + end + end + + context 'one nested parameter' do + include_context 'with one temporary file for multipart' + + let(:rewritten_fields) { rewritten_fields_hash('user[avatar]' => uploaded_filepath) } + let(:params) { { 'user' => { 'avatar' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id) } } } + + it 'builds an UploadedFile' do + expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar)) + + subject + end + end + + context 'two nested parameters' do + include_context 'with two temporary files for multipart' + + let(:rewritten_fields) { rewritten_fields_hash('user[avatar]' => uploaded_filepath, 'user[screenshot]' => uploaded_filepath2) } + let(:params) do + { + 'user' => { + 'avatar' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id), + 'screenshot' => upload_parameters_for(filepath: uploaded_filepath2, filename: filename2, remote_id: remote_id2) + } + } + end + + it 'builds UploadedFiles' do + expect_uploaded_files([ + { filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar) }, + { filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user screenshot) } + ]) + + subject + end + end + + context 'one deeply nested parameter' do + include_context 'with one temporary file for multipart' + + let(:rewritten_fields) { rewritten_fields_hash('user[avatar][bananas]' => uploaded_filepath) } + let(:params) { { 'user' => { 'avatar' => { 'bananas' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id) } } } } + + it 'builds an UploadedFile' do + expect_uploaded_files(filepath: uploaded_file, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar bananas)) + + subject + end + end + + context 'two deeply nested parameters' do + include_context 'with two temporary files for multipart' + + let(:rewritten_fields) { rewritten_fields_hash('user[avatar][bananas]' => uploaded_filepath, 'user[friend][ananas]' => uploaded_filepath2) } + let(:params) do + { + 'user' => { + 'avatar' => { + 'bananas' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id) + }, + 'friend' => { + 'ananas' => upload_parameters_for(filepath: uploaded_filepath2, filename: filename2, remote_id: remote_id2) + } + } + } + end + + it 'builds UploadedFiles' do + expect_uploaded_files([ + { filepath: uploaded_file, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar bananas) }, + { filepath: uploaded_file2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user friend ananas) } + ]) + + subject + end + end + + context 'three parameters nested at different levels' do + include_context 'with three temporary files for multipart' + + let(:rewritten_fields) do + rewritten_fields_hash( + 'file' => uploaded_filepath, + 'user[avatar]' => uploaded_filepath2, + 'user[friend][avatar]' => uploaded_filepath3 + ) + end + + let(:params) do + upload_parameters_for(filepath: uploaded_filepath, filename: filename, key: 'file', remote_id: remote_id).merge( + 'user' => { + 'avatar' => upload_parameters_for(filepath: uploaded_filepath2, filename: filename2, remote_id: remote_id2), + 'friend' => { + 'avatar' => upload_parameters_for(filepath: uploaded_filepath3, filename: filename3, remote_id: remote_id3) + } + } + ) + end + + it 'builds UploadedFiles' do + expect_uploaded_files([ + { filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file) }, + { filepath: uploaded_filepath2, original_filename: filename2, remote_id: remote_id2, size: uploaded_file2.size, params_path: %w(user avatar) }, + { filepath: uploaded_filepath3, original_filename: filename3, remote_id: remote_id3, size: uploaded_file3.size, params_path: %w(user friend avatar) } + ]) + + subject + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/project_search_results_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/project_search_results_shared_examples.rb new file mode 100644 index 00000000000..94ef41ce5a5 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/project_search_results_shared_examples.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'access restricted confidential issues' do + let(:query) { 'issue' } + let(:author) { create(:user) } + let(:assignee) { create(:user) } + let(:project) { create(:project, :internal) } + + let!(:issue) { create(:issue, project: project, title: 'Issue 1') } + let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) } + let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignees: [assignee]) } + + subject(:objects) do + described_class.new(user, query, project: project).objects('issues') + end + + context 'when the user is non-member' do + let(:user) { create(:user) } + + it 'does not list project confidential issues for non project members' do + expect(objects).to contain_exactly(issue) + expect(results.limited_issues_count).to eq 1 + end + end + + context 'when the member is guest' do + let(:user) do + create(:user) { |guest| project.add_guest(guest) } + end + + it 'does not list project confidential issues for project members with guest role' do + expect(objects).to contain_exactly(issue) + expect(results.limited_issues_count).to eq 1 + end + end + + context 'when the user is the author' do + let(:user) { author } + + it 'lists project confidential issues' do + expect(objects).to contain_exactly(issue, + security_issue_1) + expect(results.limited_issues_count).to eq 2 + end + end + + context 'when the user is the assignee' do + let(:user) { assignee } + + it 'lists project confidential issues for assignee' do + expect(objects).to contain_exactly(issue, + security_issue_2) + expect(results.limited_issues_count).to eq 2 + end + end + + context 'when the user is a developper' do + let(:user) do + create(:user) { |user| project.add_developer(user) } + end + + it 'lists project confidential issues' do + expect(objects).to contain_exactly(issue, + security_issue_1, + security_issue_2) + expect(results.limited_issues_count).to eq 3 + end + end + + context 'when the user is admin', :request_store do + let(:user) { create(:user, admin: true) } + + it 'lists all project issues' do + expect(objects).to contain_exactly(issue, + security_issue_1, + security_issue_2) + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb index 4aeae788114..025f0d5c7ea 100644 --- a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb @@ -17,5 +17,9 @@ RSpec.shared_examples 'a repo type' do it 'finds the repository for the repo type' do expect(described_class.repository_for(expected_container)).to eq(expected_repository) end + + it 'returns nil when container is nil' do + expect(described_class.repository_for(nil)).to eq(nil) + end end end diff --git a/spec/support/shared_examples/lib/gitlab/search/recent_items.rb b/spec/support/shared_examples/lib/gitlab/search/recent_items.rb new file mode 100644 index 00000000000..f96ff4b101e --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/search/recent_items.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'search recent items' do + let_it_be(:user) { create(:user) } + let_it_be(:recent_items) { described_class.new(user: user, items_limit: 5) } + let(:item) { create_item(content: 'hello world 1', project: project) } + let(:project) { create(:project, :public) } + + describe '#log_view', :clean_gitlab_redis_shared_state do + it 'adds the item to the recent items' do + recent_items.log_view(item) + + results = recent_items.search('hello') + + expect(results).to eq([item]) + end + + it 'removes an item when it exceeds the size items_limit' do + (1..6).each do |i| + recent_items.log_view(create_item(content: "item #{i}", project: project)) + end + + results = recent_items.search('item') + + expect(results.map(&:title)).to contain_exactly('item 6', 'item 5', 'item 4', 'item 3', 'item 2') + end + + it 'expires the items after expires_after' do + recent_items = described_class.new(user: user, expires_after: 0) + + recent_items.log_view(item) + + results = recent_items.search('hello') + + expect(results).to be_empty + end + + it 'does not include results logged for another user' do + another_user = create(:user) + another_item = create_item(content: 'hello world 2', project: project) + described_class.new(user: another_user).log_view(another_item) + recent_items.log_view(item) + + results = recent_items.search('hello') + + expect(results).to eq([item]) + end + end + + describe '#search', :clean_gitlab_redis_shared_state do + let(:item1) { create_item(content: "matching item 1", project: project) } + let(:item2) { create_item(content: "matching item 2", project: project) } + let(:item3) { create_item(content: "matching item 3", project: project) } + let(:non_matching_item) { create_item(content: "different item", project: project) } + let!(:non_viewed_item) { create_item(content: "matching but not viewed item", project: project) } + + before do + recent_items.log_view(item1) + recent_items.log_view(item2) + recent_items.log_view(item3) + recent_items.log_view(non_matching_item) + end + + it 'matches partial text in the item title' do + expect(recent_items.search('matching')).to contain_exactly(item1, item2, item3) + end + + it 'returns results sorted by recently viewed' do + recent_items.log_view(item2) + + expect(recent_items.search('matching')).to eq([item2, item3, item1]) + end + + it 'does not leak items you no longer have access to' do + private_project = create(:project, :public, namespace: create(:group)) + private_item = create_item(content: 'matching item title', project: private_project) + + recent_items.log_view(private_item) + + private_project.update!(visibility_level: Project::PRIVATE) + + expect(recent_items.search('matching')).not_to include(private_item) + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/search_issue_state_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_issue_state_filter_shared_examples.rb new file mode 100644 index 00000000000..e80ec516407 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/search_issue_state_filter_shared_examples.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'search results filtered by state' do + context 'state not provided' do + let(:filters) { {} } + + it 'returns opened and closed results', :aggregate_failures do + expect(results.objects(scope)).to include opened_result + expect(results.objects(scope)).to include closed_result + end + end + + context 'all state' do + let(:filters) { { state: 'all' } } + + it 'returns opened and closed results', :aggregate_failures do + expect(results.objects(scope)).to include opened_result + expect(results.objects(scope)).to include closed_result + end + end + + context 'closed state' do + let(:filters) { { state: 'closed' } } + + it 'returns only closed results', :aggregate_failures do + expect(results.objects(scope)).not_to include opened_result + expect(results.objects(scope)).to include closed_result + end + end + + context 'opened state' do + let(:filters) { { state: 'opened' } } + + it 'returns only opened results', :aggregate_failures do + expect(results.objects(scope)).to include opened_result + expect(results.objects(scope)).not_to include closed_result + end + end + + context 'unsupported state' do + let(:filters) { { state: 'hello' } } + + it 'returns only opened results', :aggregate_failures do + expect(results.objects(scope)).to include opened_result + expect(results.objects(scope)).to include closed_result + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb new file mode 100644 index 00000000000..73beef06855 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'SQL set operator' do |operator_keyword| + operator_keyword = operator_keyword.upcase + + let(:relation_1) { User.where(email: 'alice@example.com').select(:id) } + let(:relation_2) { User.where(email: 'bob@example.com').select(:id) } + + def to_sql(relation) + relation.reorder(nil).to_sql + end + + describe '.operator_keyword' do + it { expect(described_class.operator_keyword).to eq operator_keyword } + end + + describe '#to_sql' do + it "returns a String joining relations together using a #{operator_keyword}" do + set_operator = described_class.new([relation_1, relation_2]) + + expect(set_operator.to_sql).to eq("(#{to_sql(relation_1)})\n#{operator_keyword}\n(#{to_sql(relation_2)})") + end + + it 'skips Model.none segements' do + empty_relation = User.none + set_operator = described_class.new([empty_relation, relation_1, relation_2]) + + expect {User.where("users.id IN (#{set_operator.to_sql})").to_a}.not_to raise_error + expect(set_operator.to_sql).to eq("(#{to_sql(relation_1)})\n#{operator_keyword}\n(#{to_sql(relation_2)})") + end + + it "uses #{operator_keyword} ALL when removing duplicates is disabled" do + set_operator = described_class + .new([relation_1, relation_2], remove_duplicates: false) + + expect(set_operator.to_sql).to include("#{operator_keyword} ALL") + end + + it 'returns `NULL` if all relations are empty' do + empty_relation = User.none + set_operator = described_class.new([empty_relation, empty_relation]) + + expect(set_operator.to_sql).to eq('NULL') + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/incident_management_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/incident_management_activity_shared_examples.rb new file mode 100644 index 00000000000..4e35e388b23 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/incident_management_activity_shared_examples.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'an incident management tracked event' do |event| + describe ".track_event", :clean_gitlab_redis_shared_state do + let(:counter) { Gitlab::UsageDataCounters::HLLRedisCounter } + let(:start_time) { 1.minute.ago } + let(:end_time) { 1.minute.from_now } + + it "tracks the event using redis" do + # Allow other subsequent calls + allow(Gitlab::UsageDataCounters::HLLRedisCounter) + .to receive(:track_event) + + expect(Gitlab::UsageDataCounters::HLLRedisCounter) + .to receive(:track_event) + .with(current_user.id, event.to_s) + .and_call_original + + expect { subject } + .to change { counter.unique_events(event_names: event.to_s, start_date: start_time, end_date: end_time) } + .by 1 + end + end +end + +RSpec.shared_examples 'does not track incident management event' do |event| + it 'does not track the event', :clean_gitlab_redis_shared_state do + expect(Gitlab::UsageDataCounters::HLLRedisCounter) + .not_to receive(:track_event) + .with(anything, event.to_s) + end +end |