diff options
Diffstat (limited to 'spec/support/shared_examples/lib')
23 files changed, 885 insertions, 75 deletions
diff --git a/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb b/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb new file mode 100644 index 00000000000..7ace223723c --- /dev/null +++ b/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'delegates AI request to Workhorse' do |provider_flag| + context "when #{provider_flag} is disabled" do + before do + stub_feature_flags(provider_flag => false) + end + + it 'responds as not found' do + post api(url, current_user), params: input_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when ai_experimentation_api is disabled' do + before do + stub_feature_flags(ai_experimentation_api: false) + end + + it 'responds as not found' do + post api(url, current_user), params: input_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + it 'responds with Workhorse send-url headers' do + post api(url, current_user), params: input_params + + expect(response.body).to eq('""') + expect(response).to have_gitlab_http_status(:ok) + + send_url_prefix, encoded_data = response.headers['Gitlab-Workhorse-Send-Data'].split(':') + data = Gitlab::Json.parse(Base64.urlsafe_decode64(encoded_data)) + + expect(send_url_prefix).to eq('send-url') + expect(data).to eq({ + 'AllowRedirects' => false, + 'Method' => 'POST' + }.merge(expected_params)) + end +end diff --git a/spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb b/spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb new file mode 100644 index 00000000000..b88eade7db2 --- /dev/null +++ b/spec/support/shared_examples/lib/api/terraform_state_enabled_shared_examples.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'it depends on value of the `terraform_state.enabled` config' do |params = {}| + let(:expected_success_status) { params[:success_status] || :ok } + + context 'when terraform_state.enabled=false' do + before do + stub_config(terraform_state: { enabled: false }) + end + + it 'returns `forbidden` response' do + request + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when terraform_state.enabled=true' do + before do + stub_config(terraform_state: { enabled: true }) + end + + it 'returns a successful response' do + request + + expect(response).to have_gitlab_http_status(expected_success_status) + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb index d471a758f3e..c8d62205c1e 100644 --- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb +++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb @@ -1,14 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'deployment metrics examples' do - def create_deployment(args) - project = args[:project] - environment = project.environments.production.first || create(:environment, :production, project: project) - create(:deployment, :success, args.merge(environment: environment)) - - # this is needed for the DORA API so we have aggregated data - ::Dora::DailyMetrics::RefreshWorker.new.perform(environment.id, Time.current.to_date.to_s) if Gitlab.ee? - end + include CycleAnalyticsHelpers describe "#deploys" do subject { stage_summary.third } diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb index bce889b454d..5740adb3f0e 100644 --- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb @@ -68,3 +68,64 @@ RSpec.shared_examples_for 'LEFT JOIN-able value stream analytics event' do end end end + +RSpec.shared_examples_for 'value stream analytics first assignment event methods' do + let_it_be(:model1) { create(model_factory) } # rubocop: disable Rails/SaveBang + let_it_be(:model2) { create(model_factory) } # rubocop: disable Rails/SaveBang + + let_it_be(:assignment_event1) do + create(event_factory, action: :add, created_at: 3.years.ago, model_factory => model1) + end + + let_it_be(:assignment_event2) do + create(event_factory, action: :add, created_at: 2.years.ago, model_factory => model1) + end + + let_it_be(:unassignment_event1) do + create(event_factory, action: :remove, created_at: 1.year.ago, model_factory => model1) + end + + let(:query) { model1.class.where(id: [model1.id, model2.id]) } + let(:event) { described_class.new({}) } + + describe '#apply_query_customization' do + subject(:records) { event.apply_query_customization(query).pluck(:id, *event.column_list).to_a } + + it 'looks up the first assignment event timestamp' do + expect(records).to match_array([[model1.id, be_within(1.second).of(assignment_event1.created_at)]]) + end + end + + describe '#apply_negated_query_customization' do + subject(:records) { event.apply_negated_query_customization(query).pluck(:id).to_a } + + it 'returns records where the event has not happened yet' do + expect(records).to eq([model2.id]) + end + end + + describe '#include_in' do + subject(:records) { event.include_in(query).pluck(:id, *event.column_list).to_a } + + it 'returns both records' do + expect(records).to match_array([ + [model1.id, be_within(1.second).of(assignment_event1.created_at)], + [model2.id, nil] + ]) + end + + context 'when invoked multiple times' do + subject(:records) do + scope = event.include_in(query) + event.include_in(scope).pluck(:id, *event.column_list).to_a + end + + it 'returns both records' do + expect(records).to match_array([ + [model1.id, be_within(1.second).of(assignment_event1.created_at)], + [model2.id, nil] + ]) + end + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/async_constraints_validation_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/async_constraints_validation_shared_examples.rb new file mode 100644 index 00000000000..b9d71183851 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/async_constraints_validation_shared_examples.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'async constraints validation' do + include ExclusiveLeaseHelpers + + let!(:lease) { stub_exclusive_lease(lease_key, :uuid, timeout: lease_timeout) } + let(:lease_key) { "gitlab/database/asyncddl/actions/#{Gitlab::Database::PRIMARY_DATABASE_NAME}" } + let(:lease_timeout) { described_class::TIMEOUT_PER_ACTION } + + let(:constraints_model) { Gitlab::Database::AsyncConstraints::PostgresAsyncConstraintValidation } + let(:table_name) { '_test_async_constraints' } + let(:constraint_name) { 'constraint_parent_id' } + + let(:validation) do + create(:postgres_async_constraint_validation, + table_name: table_name, + name: constraint_name, + constraint_type: constraint_type) + end + + let(:connection) { validation.connection } + + subject { described_class.new(validation) } + + it 'validates the constraint while controlling statement timeout' do + allow(connection).to receive(:execute).and_call_original + expect(connection).to receive(:execute) + .with("SET statement_timeout TO '43200s'").ordered.and_call_original + expect(connection).to receive(:execute) + .with(/ALTER TABLE "#{table_name}" VALIDATE CONSTRAINT "#{constraint_name}";/).ordered.and_call_original + expect(connection).to receive(:execute) + .with("RESET statement_timeout").ordered.and_call_original + + subject.perform + end + + it 'removes the constraint validation record from table' do + expect(validation).to receive(:destroy!).and_call_original + + expect { subject.perform }.to change { constraints_model.count }.by(-1) + end + + it 'skips logic if not able to acquire exclusive lease' do + expect(lease).to receive(:try_obtain).ordered.and_return(false) + expect(connection).not_to receive(:execute).with(/ALTER TABLE/) + expect(validation).not_to receive(:destroy!) + + expect { subject.perform }.not_to change { constraints_model.count } + end + + it 'logs messages around execution' do + allow(Gitlab::AppLogger).to receive(:info).and_call_original + + subject.perform + + expect(Gitlab::AppLogger) + .to have_received(:info) + .with(a_hash_including(message: 'Starting to validate constraint')) + + expect(Gitlab::AppLogger) + .to have_received(:info) + .with(a_hash_including(message: 'Finished validating constraint')) + end + + context 'when the constraint does not exist' do + before do + connection.create_table(table_name, force: true) + end + + it 'skips validation and removes the record' do + expect(connection).not_to receive(:execute).with(/ALTER TABLE/) + + expect { subject.perform }.to change { constraints_model.count }.by(-1) + end + + it 'logs an appropriate message' do + expected_message = /Skipping #{constraint_name} validation since it does not exist/ + + allow(Gitlab::AppLogger).to receive(:info).and_call_original + + subject.perform + + expect(Gitlab::AppLogger) + .to have_received(:info) + .with(a_hash_including(message: expected_message)) + end + end + + context 'with error handling' do + before do + allow(connection).to receive(:execute).and_call_original + + allow(connection).to receive(:execute) + .with(/ALTER TABLE "#{table_name}" VALIDATE CONSTRAINT "#{constraint_name}";/) + .and_raise(ActiveRecord::StatementInvalid) + end + + context 'on production' do + before do + allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false) + end + + it 'increases execution attempts' do + expect { subject.perform }.to change { validation.attempts }.by(1) + + expect(validation.last_error).to be_present + expect(validation).not_to be_destroyed + end + + it 'logs an error message including the constraint_name' do + expect(Gitlab::AppLogger) + .to receive(:error) + .with(a_hash_including(:message, :constraint_name)) + .and_call_original + + subject.perform + end + end + + context 'on development' do + it 'also raises errors' do + expect { subject.perform } + .to raise_error(ActiveRecord::StatementInvalid) + .and change { validation.attempts }.by(1) + + expect(validation.last_error).to be_present + expect(validation).not_to be_destroyed + end + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/index_validators_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/index_validators_shared_examples.rb new file mode 100644 index 00000000000..6f0cede7130 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/index_validators_shared_examples.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples "index validators" do |validator, expected_result| + let(:structure_file_path) { Rails.root.join('spec/fixtures/structure.sql') } + let(:database_indexes) do + [ + ['wrong_index', 'CREATE UNIQUE INDEX wrong_index ON public.table_name (column_name)'], + ['extra_index', 'CREATE INDEX extra_index ON public.table_name (column_name)'], + ['index', 'CREATE UNIQUE INDEX "index" ON public.achievements USING btree (namespace_id, lower(name))'] + ] + end + + let(:inconsistency_type) { validator.name.demodulize.underscore } + + let(:database_name) { 'main' } + + let(:database_model) { Gitlab::Database.database_base_models[database_name] } + + let(:connection) { database_model.connection } + + let(:schema) { connection.current_schema } + + let(:database) { Gitlab::Database::SchemaValidation::Database.new(connection) } + let(:structure_file) { Gitlab::Database::SchemaValidation::StructureSql.new(structure_file_path, schema) } + + subject(:result) { validator.new(structure_file, database).execute } + + before do + allow(connection).to receive(:select_rows).and_return(database_indexes) + end + + it 'returns index inconsistencies' do + expect(result.map(&:object_name)).to match_array(expected_result) + expect(result.map(&:type)).to all(eql inconsistency_type) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/schema_objects_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/schema_objects_shared_examples.rb new file mode 100644 index 00000000000..ec7a881f7ce --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/schema_objects_shared_examples.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples "schema objects assertions for" do |stmt_name| + let(:stmt) { PgQuery.parse(statement).tree.stmts.first.stmt } + let(:schema_object) { described_class.new(stmt.public_send(stmt_name)) } + + describe '#name' do + it 'returns schema object name' do + expect(schema_object.name).to eq(name) + end + end + + describe '#statement' do + it 'returns schema object statement' do + expect(schema_object.statement).to eq(statement) + end + end + + describe '#table_name' do + it 'returns schema object table_name' do + expect(schema_object.table_name).to eq(table_name) + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/table_validators_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/table_validators_shared_examples.rb new file mode 100644 index 00000000000..96e58294675 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/table_validators_shared_examples.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples "table validators" do |validator, expected_result| + subject(:result) { validator.new(structure_file, database).execute } + + let(:structure_file_path) { Rails.root.join('spec/fixtures/structure.sql') } + let(:inconsistency_type) { validator.name.demodulize.underscore } + let(:database_model) { Gitlab::Database.database_base_models['main'] } + let(:connection) { database_model.connection } + let(:schema) { connection.current_schema } + let(:database) { Gitlab::Database::SchemaValidation::Database.new(connection) } + let(:structure_file) { Gitlab::Database::SchemaValidation::StructureSql.new(structure_file_path, schema) } + let(:database_tables) do + [ + { + 'table_name' => 'wrong_table', + 'column_name' => 'id', + 'not_null' => true, + 'data_type' => 'integer', + 'column_default' => "nextval('audit_events_id_seq'::regclass)" + }, + { + 'table_name' => 'wrong_table', + 'column_name' => 'description', + 'not_null' => true, + 'data_type' => 'character varying', + 'column_default' => nil + }, + { + 'table_name' => 'extra_table', + 'column_name' => 'id', + 'not_null' => true, + 'data_type' => 'integer', + 'column_default' => "nextval('audit_events_id_seq'::regclass)" + }, + { + 'table_name' => 'extra_table', + 'column_name' => 'email', + 'not_null' => true, + 'data_type' => 'character varying', + 'column_default' => nil + }, + { + 'table_name' => 'extra_table_columns', + 'column_name' => 'id', + 'not_null' => true, + 'data_type' => 'bigint', + 'column_default' => "nextval('audit_events_id_seq'::regclass)" + }, + { + 'table_name' => 'extra_table_columns', + 'column_name' => 'name', + 'not_null' => true, + 'data_type' => 'character varying(255)', + 'column_default' => nil + }, + { + 'table_name' => 'extra_table_columns', + 'column_name' => 'extra_column', + 'not_null' => true, + 'data_type' => 'character varying(255)', + 'column_default' => nil + }, + { + 'table_name' => 'missing_table_columns', + 'column_name' => 'id', + 'not_null' => true, + 'data_type' => 'bigint', + 'column_default' => 'NOT NULL' + } + ] + end + + before do + allow(connection).to receive(:exec_query).and_return(database_tables) + end + + it 'returns table inconsistencies' do + expect(result.map(&:object_name)).to match_array(expected_result) + expect(result.map(&:type)).to all(eql inconsistency_type) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/database/trigger_validators_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/trigger_validators_shared_examples.rb new file mode 100644 index 00000000000..13a112275c2 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/trigger_validators_shared_examples.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'trigger validators' do |validator, expected_result| + subject(:result) { validator.new(structure_file, database).execute } + + let(:structure_file_path) { Rails.root.join('spec/fixtures/structure.sql') } + let(:structure_file) { Gitlab::Database::SchemaValidation::StructureSql.new(structure_file_path, schema) } + let(:inconsistency_type) { validator.name.demodulize.underscore } + let(:database_name) { 'main' } + let(:schema) { 'public' } + let(:database_model) { Gitlab::Database.database_base_models[database_name] } + let(:connection) { database_model.connection } + let(:database) { Gitlab::Database::SchemaValidation::Database.new(connection) } + + let(:database_triggers) do + [ + ['trigger', 'CREATE TRIGGER trigger AFTER INSERT ON public.t1 FOR EACH ROW EXECUTE FUNCTION t1()'], + ['wrong_trigger', 'CREATE TRIGGER wrong_trigger BEFORE UPDATE ON public.t2 FOR EACH ROW EXECUTE FUNCTION t2()'], + ['extra_trigger', 'CREATE TRIGGER extra_trigger BEFORE INSERT ON public.t4 FOR EACH ROW EXECUTE FUNCTION t4()'] + ] + end + + before do + allow(connection).to receive(:select_rows).and_return(database_triggers) + end + + it 'returns trigger inconsistencies' do + expect(result.map(&:object_name)).to match_array(expected_result) + expect(result.map(&:type)).to all(eql inconsistency_type) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb index f26b9a4a7bd..d388abb16c6 100644 --- a/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/gitaly_client_shared_examples.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true def raw_repo_without_container(repository) - Gitlab::Git::Repository.new(repository.shard, - "#{repository.disk_path}.git", - repository.repo_type.identifier_for_container(repository.container), - repository.container.full_path) + Gitlab::Git::Repository.new( + repository.shard, + "#{repository.disk_path}.git", + repository.repo_type.identifier_for_container(repository.container), + repository.container.full_path + ) end RSpec.shared_examples 'Gitaly feature flag actors are inferred from repository' do diff --git a/spec/support/shared_examples/lib/gitlab/json_logger_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/json_logger_shared_examples.rb new file mode 100644 index 00000000000..8a5e8397c3d --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/json_logger_shared_examples.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a json logger' do |extra_params| + let(:now) { Time.now } + let(:correlation_id) { Labkit::Correlation::CorrelationId.current_id } + + it 'formats strings' do + output = subject.format_message('INFO', now, 'test', 'Hello world') + data = Gitlab::Json.parse(output) + + expect(data['severity']).to eq('INFO') + expect(data['time']).to eq(now.utc.iso8601(3)) + expect(data['message']).to eq('Hello world') + expect(data['correlation_id']).to eq(correlation_id) + expect(data).to include(extra_params) + end + + it 'formats hashes' do + output = subject.format_message('INFO', now, 'test', { hello: 1 }) + data = Gitlab::Json.parse(output) + + expect(data['severity']).to eq('INFO') + expect(data['time']).to eq(now.utc.iso8601(3)) + expect(data['hello']).to eq(1) + expect(data['message']).to be_nil + expect(data['correlation_id']).to eq(correlation_id) + expect(data).to include(extra_params) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb index 27ca27a9035..4b0e3234750 100644 --- a/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb @@ -8,9 +8,9 @@ RSpec.shared_examples 'local and remote storage migration' do where(:start_store, :end_store, :method) do ObjectStorage::Store::LOCAL | ObjectStorage::Store::REMOTE | :migrate_to_remote_storage - ObjectStorage::Store::REMOTE | ObjectStorage::Store::REMOTE | :migrate_to_remote_storage # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + ObjectStorage::Store::REMOTE | ObjectStorage::Store::REMOTE | :migrate_to_remote_storage ObjectStorage::Store::REMOTE | ObjectStorage::Store::LOCAL | :migrate_to_local_storage - ObjectStorage::Store::LOCAL | ObjectStorage::Store::LOCAL | :migrate_to_local_storage # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + ObjectStorage::Store::LOCAL | ObjectStorage::Store::LOCAL | :migrate_to_local_storage end with_them do 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 index f83fecee4ea..0016f1e670d 100644 --- 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 @@ -38,8 +38,7 @@ RSpec.shared_examples 'access restricted confidential issues' do let(:user) { author } it 'lists project confidential issues' do - expect(objects).to contain_exactly(issue, - security_issue_1) + expect(objects).to contain_exactly(issue, security_issue_1) expect(results.limited_issues_count).to eq 2 end end @@ -48,8 +47,7 @@ RSpec.shared_examples 'access restricted confidential issues' do let(:user) { assignee } it 'lists project confidential issues for assignee' do - expect(objects).to contain_exactly(issue, - security_issue_2) + expect(objects).to contain_exactly(issue, security_issue_2) expect(results.limited_issues_count).to eq 2 end end @@ -60,9 +58,7 @@ RSpec.shared_examples 'access restricted confidential issues' do end it 'lists project confidential issues' do - expect(objects).to contain_exactly(issue, - security_issue_1, - security_issue_2) + expect(objects).to contain_exactly(issue, security_issue_1, security_issue_2) expect(results.limited_issues_count).to eq 3 end end @@ -72,9 +68,7 @@ RSpec.shared_examples 'access restricted confidential issues' do context 'when admin mode is enabled', :enable_admin_mode do it 'lists all project issues' do - expect(objects).to contain_exactly(issue, - security_issue_1, - security_issue_2) + expect(objects).to contain_exactly(issue, security_issue_1, security_issue_2) 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 025f0d5c7ea..c2898513424 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 @@ -15,7 +15,7 @@ RSpec.shared_examples 'a repo type' do describe '#repository_for' do it 'finds the repository for the repo type' do - expect(described_class.repository_for(expected_container)).to eq(expected_repository) + expect(described_class.repository_for(expected_repository_resolver)).to eq(expected_repository) end it 'returns nil when container is nil' do diff --git a/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb index a3e4379f4d3..18545698c27 100644 --- a/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/search_language_filter_shared_examples.rb @@ -26,29 +26,4 @@ RSpec.shared_examples 'search results filtered by language' do expect(blob_results.size).to eq(5) expect(paths).to match_array(expected_paths) end - - context 'when the search_blobs_language_aggregation feature flag is disabled' do - before do - stub_feature_flags(search_blobs_language_aggregation: false) - end - - it 'does not filter by language', :sidekiq_inline, :aggregate_failures do - expected_paths = %w[ - CHANGELOG - CONTRIBUTING.md - bar/branch-test.txt - custom-highlighting/test.gitlab-custom - files/ruby/popen.rb - files/ruby/regex.rb - files/ruby/version_info.rb - files/whitespace - encoding/test.txt - files/markdown/ruby-style-guide.md - ] - - paths = blob_results.map { |blob| blob.binary_path } - expect(blob_results.size).to eq(10) - expect(paths).to match_array(expected_paths) - end - end end diff --git a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb index ff03051ed37..74570a4da5c 100644 --- a/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb @@ -5,7 +5,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name| instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, duplicate_key_ttl: Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob::DEFAULT_DUPLICATE_KEY_TTL) end - let(:expected_message) { "dropped #{strategy_name.to_s.humanize.downcase}" } + let(:humanized_strategy_name) { strategy_name.to_s.humanize.downcase } subject(:strategy) { Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies.for(strategy_name).new(fake_duplicate_job) } @@ -155,7 +155,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name| fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger) expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger) - expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, {}) + expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), humanized_strategy_name, {}) strategy.schedule({ 'jid' => 'new jid' }) {} end @@ -165,7 +165,7 @@ RSpec.shared_examples 'deduplicating jobs when scheduling' do |strategy_name| expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger) allow(fake_duplicate_job).to receive(:options).and_return({ foo: :bar }) - expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), expected_message, { foo: :bar }) + expect(fake_logger).to receive(:deduplicated_log).with(a_hash_including({ 'jid' => 'new jid' }), humanized_strategy_name, { foo: :bar }) strategy.schedule({ 'jid' => 'new jid' }) {} end diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb index d4802a19202..169fceced7a 100644 --- a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events for given event params' do +RSpec.shared_examples 'tracked issuable snowplow and service ping events for given event params' do before do stub_application_setting(usage_ping_enabled: true) end - def count_unique(date_from: 1.minute.ago, date_to: 1.minute.from_now) + def count_unique(date_from: Date.today.beginning_of_week, date_to: 1.week.from_now) Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to) end @@ -27,35 +27,23 @@ RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events expect_snowplow_event(**{ category: category, action: event_action, user: user1 }.merge(event_params)) end - - context 'with route_hll_to_snowplow_phase2 disabled' do - before do - stub_feature_flags(route_hll_to_snowplow_phase2: false) - end - - it 'does not emit snowplow event' do - track_action({ author: user1 }.merge(track_params)) - - expect_no_snowplow_event - end - end end -RSpec.shared_examples 'daily tracked issuable snowplow and service ping events with project' do - it_behaves_like 'a daily tracked issuable snowplow and service ping events for given event params' do +RSpec.shared_examples 'tracked issuable snowplow and service ping events with project' do + it_behaves_like 'tracked issuable snowplow and service ping events for given event params' do let(:context) do Gitlab::Tracking::ServicePingContext .new(data_source: :redis_hll, event: event_property) .to_h end - let(:track_params) { { project: project } } - let(:event_params) { track_params.merge(label: event_label, property: event_property, namespace: project.namespace, context: [context]) } + let(:track_params) { original_params || { project: project } } + let(:event_params) { { project: project }.merge(label: event_label, property: event_property, namespace: project.namespace, context: [context]) } end end -RSpec.shared_examples 'a daily tracked issuable snowplow and service ping events with namespace' do - it_behaves_like 'a daily tracked issuable snowplow and service ping events for given event params' do +RSpec.shared_examples 'tracked issuable snowplow and service ping events with namespace' do + it_behaves_like 'tracked issuable snowplow and service ping events for given event params' do let(:context) do Gitlab::Tracking::ServicePingContext .new(data_source: :redis_hll, event: event_property) diff --git a/spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb new file mode 100644 index 00000000000..a42d1450e4d --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/utils/username_and_email_generator_shared_examples.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'username and email pair is generated by Gitlab::Utils::UsernameAndEmailGenerator' do + let(:randomhex) { 'randomhex' } + + it 'check email domain' do + expect(subject.email).to end_with("@#{email_domain}") + end + + it 'contains SecureRandom part' do + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + + expect(subject.username).to include("_#{randomhex}") + expect(subject.email).to include("_#{randomhex}@") + end + + it 'email name is the same as username' do + expect(subject.email).to include("#{subject.username}@") + end + + context 'when conflicts' do + let(:reserved_username) { "#{username_prefix}_#{randomhex}" } + let(:reserved_email) { "#{reserved_username}@#{email_domain}" } + + shared_examples 'uniquifies username and email' do + it 'uniquifies username and email' do + expect(subject.username).to eq("#{reserved_username}1") + expect(subject.email).to include("#{subject.username}@") + end + end + + context 'when username is reserved' do + context 'when username is reserved by user' do + before do + create(:user, username: reserved_username) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + + context 'when it conflicts with top-level group namespace' do + before do + create(:group, path: reserved_username) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + + context 'when it conflicts with top-level group namespace that includes upcased characters' do + before do + create(:group, path: reserved_username.upcase) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + end + + context 'when email is reserved' do + context 'when it conflicts with confirmed primary email' do + before do + create(:user, email: reserved_email) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + + context 'when it conflicts with unconfirmed primary email' do + before do + create(:user, :unconfirmed, email: reserved_email) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + + context 'when it conflicts with confirmed secondary email' do + before do + create(:email, :confirmed, email: reserved_email) + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + include_examples 'uniquifies username and email' + end + end + + context 'when email and username is reserved' do + before do + create(:user, email: reserved_email) + create(:user, username: "#{reserved_username}1") + allow(SecureRandom).to receive(:hex).at_least(:once).and_return(randomhex) + end + + it 'uniquifies username and email' do + expect(subject.username).to eq("#{reserved_username}2") + + expect(subject.email).to include("#{subject.username}@") + end + end + end +end diff --git a/spec/support/shared_examples/lib/menus_shared_examples.rb b/spec/support/shared_examples/lib/menus_shared_examples.rb index 2c2cb362b07..0aa98517444 100644 --- a/spec/support/shared_examples/lib/menus_shared_examples.rb +++ b/spec/support/shared_examples/lib/menus_shared_examples.rb @@ -37,3 +37,58 @@ RSpec.shared_examples_for 'pill_count formatted results' do expect(pill_count).to eq('112.6k') end end + +RSpec.shared_examples_for 'serializable as super_sidebar_menu_args' do + let(:extra_attrs) { raise NotImplementedError } + + it 'returns hash with provided attributes' do + expect(menu.serialize_as_menu_item_args).to eq({ + title: menu.title, + link: menu.link, + active_routes: menu.active_routes, + container_html_options: menu.container_html_options, + **extra_attrs + }) + end + + it 'returns hash with an item_id' do + expect(menu.serialize_as_menu_item_args[:item_id]).not_to be_nil + end +end + +RSpec.shared_examples_for 'not serializable as super_sidebar_menu_args' do + it 'returns nil' do + expect(menu.serialize_as_menu_item_args).to be_nil + end +end + +RSpec.shared_examples_for 'a panel with uniquely identifiable menu items' do + let(:menu_items) do + subject.instance_variable_get(:@menus) + .flat_map { |menu| menu.instance_variable_get(:@items) } + end + + it 'all menu_items have unique item_id' do + duplicated_ids = menu_items.group_by(&:item_id).reject { |_, v| (v.size < 2) } + + expect(duplicated_ids).to eq({}) + end + + it 'all menu_items have an item_id' do + items_with_nil_id = menu_items.select { |item| item.item_id.nil? } + + expect(items_with_nil_id).to match_array([]) + end +end + +RSpec.shared_examples_for 'a panel with all menu_items categorized' do + let(:uncategorized_menu) do + subject.instance_variable_get(:@menus) + .find { |menu| menu.instance_of?(::Sidebars::UncategorizedMenu) } + end + + it 'has no uncategorized menu_items' do + uncategorized_menu_items = uncategorized_menu.instance_variable_get(:@items) + expect(uncategorized_menu_items).to eq([]) + end +end diff --git a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb index e0b411e1e2a..fa3e9bf5340 100644 --- a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb +++ b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb @@ -90,7 +90,9 @@ RSpec.shared_examples 'Sentry API response size limit' do end it 'raises an exception when response is too large' do - expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError, - 'Sentry API response is too big. Limit is 1 MB.') + expect { subject }.to raise_error( + ErrorTracking::SentryClient::ResponseInvalidSizeError, + 'Sentry API response is too big. Limit is 1 MB.' + ) end end diff --git a/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb new file mode 100644 index 00000000000..f913c6b8a9e --- /dev/null +++ b/spec/support/shared_examples/lib/sidebars/admin/menus/admin_menus_shared_examples.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'Admin menu' do |link:, title:, icon:, separated: false| + let_it_be(:user) { build(:user, :admin) } + + before do + allow(user).to receive(:can_admin_all_resources?).and_return(true) + end + + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + subject { described_class.new(context) } + + it 'renders the correct link' do + expect(subject.link).to match link + end + + it 'renders the correct title' do + expect(subject.title).to eq title + end + + it 'renders the correct icon' do + expect(subject.sprite_icon).to be icon + end + + it 'renders the separator if needed' do + expect(subject.separated?).to be separated + end + + describe '#render?' do + context 'when user is admin' do + it 'renders' do + expect(subject.render?).to be true + end + end + + context 'when user is not admin' do + it 'does not render' do + expect(described_class.new(Sidebars::Context.new(current_user: build(:user), + container: nil)).render?).to be false + end + end + + context 'when user is not logged in' do + it 'does not render' do + expect(described_class.new(Sidebars::Context.new(current_user: nil, container: nil)).render?).to be false + end + end + end +end + +RSpec.shared_examples 'Admin menu without sub menus' do |active_routes:| + let_it_be(:user) { build(:user, :admin) } + + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + subject { described_class.new(context) } + + it 'does not contain any sub menu(s)' do + expect(subject.has_items?).to be false + end + + it 'defines correct active route' do + expect(subject.active_routes).to eq active_routes + end +end + +RSpec.shared_examples 'Admin menu with sub menus' do + let_it_be(:user) { build(:user, :admin) } + + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + subject { described_class.new(context) } + + it 'contains submemus' do + expect(subject.has_items?).to be true + end +end diff --git a/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb new file mode 100644 index 00000000000..5e8aebb4f29 --- /dev/null +++ b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'User profile menu' do |title:, icon:, active_route:| + let_it_be(:current_user) { build(:user) } + let_it_be(:user) { build(:user) } + + let(:context) { Sidebars::Context.new(current_user: current_user, container: user) } + + subject { described_class.new(context) } + + it 'does not contain any sub menu' do + expect(subject.has_items?).to be false + end + + it 'renders the correct link' do + expect(subject.link).to match link + end + + it 'renders the correct title' do + expect(subject.title).to eq title + end + + it 'renders the correct icon' do + expect(subject.sprite_icon).to eq icon + end + + it 'defines correct active route' do + expect(subject.active_routes[:path]).to be active_route + end + + it 'renders if user is logged in' do + expect(subject.render?).to be true + end + + [:blocked, :banned].each do |trait| + context "when viewed user is #{trait}" do + let_it_be(:viewed_user) { build(:user, trait) } + let(:context) { Sidebars::Context.new(current_user: user, container: viewed_user) } + + context 'when user is not logged in' do + it 'is not allowed to view the menu item' do + expect(described_class.new(Sidebars::Context.new(current_user: nil, + container: viewed_user)).render?).to be false + end + end + + context 'when current user has permission' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_user_profile, viewed_user).and_return(true) + end + + it 'is allowed to view the menu item' do + expect(described_class.new(context).render?).to be true + end + end + + context 'when current user does not have permission' do + it 'is not allowed to view the menu item' do + expect(described_class.new(context).render?).to be false + end + end + end + end +end + +RSpec.shared_examples 'Followers/followees counts' do |symbol| + let_it_be(:current_user) { build(:user) } + let_it_be(:user) { build(:user) } + + let(:context) { Sidebars::Context.new(current_user: current_user, container: user) } + + subject { described_class.new(context) } + + context 'when there are items' do + before do + allow(user).to receive(symbol).and_return([1, 2]) + end + + it 'renders the pill' do + expect(subject.has_pill?).to be(true) + end + + it 'returns the count' do + expect(subject.pill_count).to be(2) + end + end + + context 'when there are no items' do + it 'does not render the pill' do + expect(subject.has_pill?).to be(false) + end + end +end diff --git a/spec/support/shared_examples/lib/sidebars/user_settings/menus/user_settings_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/user_settings/menus/user_settings_menus_shared_examples.rb new file mode 100644 index 00000000000..b91386d1935 --- /dev/null +++ b/spec/support/shared_examples/lib/sidebars/user_settings/menus/user_settings_menus_shared_examples.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'User settings menu' do |link:, title:, icon:, active_routes:| + let_it_be(:user) { create(:user) } + + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + subject { described_class.new(context) } + + it 'does not contain any sub menu' do + expect(subject.has_items?).to be false + end + + it 'renders the correct link' do + expect(subject.link).to match link + end + + it 'renders the correct title' do + expect(subject.title).to eq title + end + + it 'renders the correct icon' do + expect(subject.sprite_icon).to be icon + end + + it 'defines correct active route' do + expect(subject.active_routes).to eq active_routes + end +end + +RSpec.shared_examples 'User settings menu #render? method' do + describe '#render?' do + subject { described_class.new(context) } + + context 'when user is logged in' do + let_it_be(:user) { build(:user) } + let(:context) { Sidebars::Context.new(current_user: user, container: nil) } + + it 'renders' do + expect(subject.render?).to be true + end + end + + context 'when user is not logged in' do + let(:context) { Sidebars::Context.new(current_user: nil, container: nil) } + + it 'does not render' do + expect(subject.render?).to be false + end + end + end +end |