diff options
Diffstat (limited to 'spec/lib')
268 files changed, 5641 insertions, 2097 deletions
diff --git a/spec/lib/api/entities/basic_project_details_spec.rb b/spec/lib/api/entities/basic_project_details_spec.rb index dc7c4fdce4e..8419eb0a932 100644 --- a/spec/lib/api/entities/basic_project_details_spec.rb +++ b/spec/lib/api/entities/basic_project_details_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe API::Entities::BasicProjectDetails do let_it_be(:project) { create(:project) } - let(:current_user) { project.owner } + let(:current_user) { project.first_owner } subject(:output) { described_class.new(project, current_user: current_user).as_json } diff --git a/spec/lib/api/entities/deployment_extended_spec.rb b/spec/lib/api/entities/deployment_extended_spec.rb new file mode 100644 index 00000000000..733c47362be --- /dev/null +++ b/spec/lib/api/entities/deployment_extended_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Entities::DeploymentExtended do + describe '#as_json' do + subject { described_class.new(deployment).as_json } + + let(:deployment) { create(:deployment) } + + it 'includes fields from deployment entity' do + is_expected.to include(:id, :iid, :ref, :sha, :created_at, :updated_at, :user, :environment, :deployable, :status) + end + end +end diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb index 2277bd78e86..b2d4a3094af 100644 --- a/spec/lib/api/helpers_spec.rb +++ b/spec/lib/api/helpers_spec.rb @@ -76,6 +76,12 @@ RSpec.describe API::Helpers do expect(subject.find_project(non_existing_id)).to be_nil end end + + context 'when project id is not provided' do + it 'returns nil' do + expect(subject.find_project(nil)).to be_nil + end + end end context 'when ID is used as an argument' do @@ -160,7 +166,7 @@ RSpec.describe API::Helpers do describe '#find_project!' do let_it_be(:project) { create(:project) } - let(:user) { project.owner} + let(:user) { project.first_owner} before do allow(subject).to receive(:current_user).and_return(user) diff --git a/spec/lib/backup/database_spec.rb b/spec/lib/backup/database_spec.rb index f57037d5652..4345778ba92 100644 --- a/spec/lib/backup/database_spec.rb +++ b/spec/lib/backup/database_spec.rb @@ -6,6 +6,10 @@ RSpec.describe Backup::Database do let(:progress) { StringIO.new } let(:output) { progress.string } + before do + allow(Gitlab::TaskHelpers).to receive(:ask_to_continue) + end + describe '#restore' do let(:cmd) { %W[#{Gem.ruby} -e $stdout.puts(1)] } let(:data) { Rails.root.join("spec/fixtures/pages_empty.tar.gz").to_s } @@ -20,7 +24,7 @@ RSpec.describe Backup::Database do let(:data) { Rails.root.join("spec/fixtures/pages_empty.tar.gz").to_s } it 'returns successfully' do - expect(subject.restore).to eq([]) + subject.restore expect(output).to include("Restoring PostgreSQL database") expect(output).to include("[DONE]") @@ -42,7 +46,8 @@ RSpec.describe Backup::Database do let(:cmd) { %W[#{Gem.ruby} -e $stderr.write("#{noise}#{visible_error}")] } it 'filters out noise from errors' do - expect(subject.restore).to eq([visible_error]) + subject.restore + expect(output).to include("ERRORS") expect(output).not_to include(noise) expect(output).to include(visible_error) diff --git a/spec/lib/backup/gitaly_backup_spec.rb b/spec/lib/backup/gitaly_backup_spec.rb index cd0d984fbdb..6bf4f833c1f 100644 --- a/spec/lib/backup/gitaly_backup_spec.rb +++ b/spec/lib/backup/gitaly_backup_spec.rb @@ -38,7 +38,7 @@ RSpec.describe Backup::GitalyBackup do create(:wiki_page, container: project) create(:design, :with_file, issue: create(:issue, project: project)) project_snippet = create(:project_snippet, :repository, project: project) - personal_snippet = create(:personal_snippet, :repository, author: project.owner) + personal_snippet = create(:personal_snippet, :repository, author: project.first_owner) expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything).and_call_original @@ -122,8 +122,8 @@ RSpec.describe Backup::GitalyBackup do context 'restore' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) } - let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) } + let_it_be(:personal_snippet) { create(:personal_snippet, author: project.first_owner) } + let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.first_owner) } def copy_bundle_to_backup_path(bundle_name, destination) FileUtils.mkdir_p(File.join(Gitlab.config.backup.path, 'repositories', File.dirname(destination))) diff --git a/spec/lib/backup/gitaly_rpc_backup_spec.rb b/spec/lib/backup/gitaly_rpc_backup_spec.rb index 14f9d27ca6e..4829d51ac9d 100644 --- a/spec/lib/backup/gitaly_rpc_backup_spec.rb +++ b/spec/lib/backup/gitaly_rpc_backup_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Backup::GitalyRpcBackup do create(:wiki_page, container: project) create(:design, :with_file, issue: create(:issue, project: project)) project_snippet = create(:project_snippet, :repository, project: project) - personal_snippet = create(:personal_snippet, :repository, author: project.owner) + personal_snippet = create(:personal_snippet, :repository, author: project.first_owner) subject.start(:create) subject.enqueue(project, Gitlab::GlRepository::PROJECT) @@ -75,8 +75,8 @@ RSpec.describe Backup::GitalyRpcBackup do context 'restore' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) } - let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) } + let_it_be(:personal_snippet) { create(:personal_snippet, author: project.first_owner) } + let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.first_owner) } def copy_bundle_to_backup_path(bundle_name, destination) FileUtils.mkdir_p(File.join(Gitlab.config.backup.path, 'repositories', File.dirname(destination))) diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index 31cc3012eb1..ac693ad8b98 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -12,6 +12,11 @@ RSpec.describe Backup::Manager do before do allow(progress).to receive(:puts) allow(progress).to receive(:print) + FileUtils.mkdir_p('tmp/tests/public/uploads') + end + + after do + FileUtils.rm_rf('tmp/tests/public/uploads', secure: true) end describe '#pack' do @@ -409,7 +414,7 @@ RSpec.describe Backup::Manager do # the Fog mock only knows about directories we create explicitly connection = ::Fog::Storage.new(Gitlab.config.backup.upload.connection.symbolize_keys) - connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) + connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) # rubocop:disable Rails/SaveBang end context 'target path' do @@ -455,7 +460,7 @@ RSpec.describe Backup::Manager do } ) - connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) + connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) # rubocop:disable Rails/SaveBang end context 'with SSE-S3 without using storage_options' do @@ -521,7 +526,7 @@ RSpec.describe Backup::Manager do ) connection = ::Fog::Storage.new(Gitlab.config.backup.upload.connection.symbolize_keys) - connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) + connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) # rubocop:disable Rails/SaveBang end it 'does not attempt to set ACL' do diff --git a/spec/lib/backup/repositories_spec.rb b/spec/lib/backup/repositories_spec.rb index f3830da344b..0b29a25360d 100644 --- a/spec/lib/backup/repositories_spec.rb +++ b/spec/lib/backup/repositories_spec.rb @@ -6,8 +6,17 @@ RSpec.describe Backup::Repositories do let(:progress) { spy(:stdout) } let(:parallel_enqueue) { true } let(:strategy) { spy(:strategy, parallel_enqueue?: parallel_enqueue) } - - subject { described_class.new(progress, strategy: strategy) } + let(:max_concurrency) { 1 } + let(:max_storage_concurrency) { 1 } + + subject do + described_class.new( + progress, + strategy: strategy, + max_concurrency: max_concurrency, + max_storage_concurrency: max_storage_concurrency + ) + end describe '#dump' do let_it_be(:projects) { create_list(:project, 5, :repository) } @@ -15,9 +24,9 @@ RSpec.describe Backup::Repositories do RSpec.shared_examples 'creates repository bundles' do it 'calls enqueue for each repository type', :aggregate_failures do project_snippet = create(:project_snippet, :repository, project: project) - personal_snippet = create(:personal_snippet, :repository, author: project.owner) + personal_snippet = create(:personal_snippet, :repository, author: project.first_owner) - subject.dump(max_concurrency: 1, max_storage_concurrency: 1) + subject.dump expect(strategy).to have_received(:start).with(:create) expect(strategy).to have_received(:enqueue).with(project, Gitlab::GlRepository::PROJECT) @@ -51,38 +60,40 @@ RSpec.describe Backup::Repositories do end expect(strategy).to receive(:finish!) - subject.dump(max_concurrency: 1, max_storage_concurrency: 1) + subject.dump end describe 'command failure' do it 'enqueue_project raises an error' do allow(strategy).to receive(:enqueue).with(anything, Gitlab::GlRepository::PROJECT).and_raise(IOError) - expect { subject.dump(max_concurrency: 1, max_storage_concurrency: 1) }.to raise_error(IOError) + expect { subject.dump }.to raise_error(IOError) end it 'project query raises an error' do allow(Project).to receive_message_chain(:includes, :find_each).and_raise(ActiveRecord::StatementTimeout) - expect { subject.dump(max_concurrency: 1, max_storage_concurrency: 1) }.to raise_error(ActiveRecord::StatementTimeout) + expect { subject.dump }.to raise_error(ActiveRecord::StatementTimeout) end end it 'avoids N+1 database queries' do control_count = ActiveRecord::QueryRecorder.new do - subject.dump(max_concurrency: 1, max_storage_concurrency: 1) + subject.dump end.count create_list(:project, 2, :repository) expect do - subject.dump(max_concurrency: 1, max_storage_concurrency: 1) + subject.dump end.not_to exceed_query_limit(control_count) end end context 'concurrency with a strategy without parallel enqueueing support' do let(:parallel_enqueue) { false } + let(:max_concurrency) { 2 } + let(:max_storage_concurrency) { 2 } it 'enqueues all projects sequentially' do expect(Thread).not_to receive(:new) @@ -93,13 +104,14 @@ RSpec.describe Backup::Repositories do end expect(strategy).to receive(:finish!) - subject.dump(max_concurrency: 2, max_storage_concurrency: 2) + subject.dump end end [4, 10].each do |max_storage_concurrency| context "max_storage_concurrency #{max_storage_concurrency}", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/241701' do let(:storage_keys) { %w[default test_second_storage] } + let(:max_storage_concurrency) { max_storage_concurrency } before do allow(Gitlab.config.repositories.storages).to receive(:keys).and_return(storage_keys) @@ -116,54 +128,58 @@ RSpec.describe Backup::Repositories do end expect(strategy).to receive(:finish!) - subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency) + subject.dump end - it 'creates the expected number of threads with extra max concurrency' do - expect(Thread).to receive(:new) - .exactly(storage_keys.length * (max_storage_concurrency + 1)).times - .and_call_original + context 'with extra max concurrency' do + let(:max_concurrency) { 3 } - expect(strategy).to receive(:start).with(:create) - projects.each do |project| - expect(strategy).to receive(:enqueue).with(project, Gitlab::GlRepository::PROJECT) - end - expect(strategy).to receive(:finish!) + it 'creates the expected number of threads' do + expect(Thread).to receive(:new) + .exactly(storage_keys.length * (max_storage_concurrency + 1)).times + .and_call_original - subject.dump(max_concurrency: 3, max_storage_concurrency: max_storage_concurrency) + expect(strategy).to receive(:start).with(:create) + projects.each do |project| + expect(strategy).to receive(:enqueue).with(project, Gitlab::GlRepository::PROJECT) + end + expect(strategy).to receive(:finish!) + + subject.dump + end end describe 'command failure' do it 'enqueue_project raises an error' do allow(strategy).to receive(:enqueue).and_raise(IOError) - expect { subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency) }.to raise_error(IOError) + expect { subject.dump }.to raise_error(IOError) end it 'project query raises an error' do allow(Project).to receive_message_chain(:for_repository_storage, :includes, :find_each).and_raise(ActiveRecord::StatementTimeout) - expect { subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency) }.to raise_error(ActiveRecord::StatementTimeout) + expect { subject.dump }.to raise_error(ActiveRecord::StatementTimeout) end context 'misconfigured storages' do let(:storage_keys) { %w[test_second_storage] } it 'raises an error' do - expect { subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency) }.to raise_error(Backup::Error, 'repositories.storages in gitlab.yml is misconfigured') + expect { subject.dump }.to raise_error(Backup::Error, 'repositories.storages in gitlab.yml is misconfigured') end end end it 'avoids N+1 database queries' do control_count = ActiveRecord::QueryRecorder.new do - subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency) + subject.dump end.count create_list(:project, 2, :repository) expect do - subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency) + subject.dump end.not_to exceed_query_limit(control_count) end end @@ -172,8 +188,8 @@ RSpec.describe Backup::Repositories do describe '#restore' do let_it_be(:project) { create(:project) } - let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) } - let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) } + let_it_be(:personal_snippet) { create(:personal_snippet, author: project.first_owner) } + let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.first_owner) } it 'calls enqueue for each repository type', :aggregate_failures do subject.restore diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb index 24d13bdb42c..036817834d5 100644 --- a/spec/lib/banzai/filter/external_link_filter_spec.rb +++ b/spec/lib/banzai/filter/external_link_filter_spec.rb @@ -71,6 +71,13 @@ RSpec.describe Banzai::Filter::ExternalLinkFilter do expect(doc.to_html).to eq(expected) end + + it 'adds rel and target attributes to improperly formatted protocols' do + doc = filter %q(<p><a target="_blank" href="http:evil.com">Reverse Tabnabbing</a></p>) + expected = %q(<p><a target="_blank" href="http:evil.com" rel="nofollow noreferrer noopener">Reverse Tabnabbing</a></p>) + + expect(doc.to_html).to eq(expected) + end end context 'for links with a username' do diff --git a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb index b3523a25116..c493cb77c98 100644 --- a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb @@ -515,7 +515,7 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do enable_design_management(enabled) end - let(:current_user) { project.owner } + let(:current_user) { project.first_owner } let(:enabled) { true } let(:matches) { Issue.link_reference_pattern.match(input_text) } let(:extras) { subject.object_link_text_extras(issue, matches) } diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb index 6e90f4457fa..91c644cb16a 100644 --- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb +++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb @@ -91,6 +91,12 @@ RSpec.describe Banzai::Filter::TableOfContentsFilter do # ExternalLinkFilter (see https://gitlab.com/gitlab-org/gitlab/issues/26210) expect(doc.css('h1 a').first.attr('href')).to eq "##{CGI.escape('한글')}" end + + it 'limits header href length with 255 characters' do + doc = filter(header(1, 'a' * 500)) + + expect(doc.css('h1 a').first.attr('href')).to eq "##{'a' * 255}" + end end end diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index e64ab5dfce3..8f69480c65f 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Banzai::ObjectRenderer do let(:project) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:renderer) do described_class.new( default_project: project, diff --git a/spec/lib/bitbucket_server/representation/repo_spec.rb b/spec/lib/bitbucket_server/representation/repo_spec.rb index 7a773f47ca5..5de4360bbd0 100644 --- a/spec/lib/bitbucket_server/representation/repo_spec.rb +++ b/spec/lib/bitbucket_server/representation/repo_spec.rb @@ -9,6 +9,7 @@ RSpec.describe BitbucketServer::Representation::Repo do "slug": "rouge", "id": 1, "name": "rouge", + "description": "Rogue Repo", "scmId": "git", "state": "AVAILABLE", "statusMessage": "Available", @@ -17,7 +18,7 @@ RSpec.describe BitbucketServer::Representation::Repo do "key": "TEST", "id": 1, "name": "test", - "description": "Test", + "description": "Test Project", "public": false, "type": "NORMAL", "links": { @@ -73,7 +74,7 @@ RSpec.describe BitbucketServer::Representation::Repo do end describe '#description' do - it { expect(subject.description).to eq('Test') } + it { expect(subject.description).to eq('Rogue Repo') } end describe '#full_name' do diff --git a/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb b/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb index 80607485b6e..50c54a7b47f 100644 --- a/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb +++ b/spec/lib/bulk_imports/common/extractors/graphql_extractor_spec.rb @@ -8,12 +8,15 @@ RSpec.describe BulkImports::Common::Extractors::GraphqlExtractor do let(:response) { double(original_hash: { 'data' => { 'foo' => 'bar' }, 'page_info' => {} }) } let(:options) do { - query: double( - to_s: 'test', - variables: {}, - data_path: %w[data foo], - page_info_path: %w[data page_info] - ) + query: + double( + new: double( + to_s: 'test', + variables: {}, + data_path: %w[data foo], + page_info_path: %w[data page_info] + ) + ) } end diff --git a/spec/lib/bulk_imports/common/graphql/get_members_query_spec.rb b/spec/lib/bulk_imports/common/graphql/get_members_query_spec.rb new file mode 100644 index 00000000000..e3a7335a238 --- /dev/null +++ b/spec/lib/bulk_imports/common/graphql/get_members_query_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe BulkImports::Common::Graphql::GetMembersQuery do + let(:entity) { create(:bulk_import_entity, :group_entity) } + let(:tracker) { create(:bulk_import_tracker, entity: entity) } + let(:context) { BulkImports::Pipeline::Context.new(tracker) } + + subject(:query) { described_class.new(context: context) } + + it 'has a valid query' do + parsed_query = GraphQL::Query.new( + GitlabSchema, + query.to_s, + variables: query.variables + ) + result = GitlabSchema.static_validator.validate(parsed_query) + + expect(result[:errors]).to be_empty + end + + describe '#data_path' do + it 'returns data path' do + expected = %w[data portable members nodes] + + expect(query.data_path).to eq(expected) + end + end + + describe '#page_info_path' do + it 'returns pagination information path' do + expected = %w[data portable members page_info] + + expect(query.page_info_path).to eq(expected) + end + end + + describe '#to_s' do + context 'when entity is group' do + it 'queries group & group members' do + expect(query.to_s).to include('group') + expect(query.to_s).to include('groupMembers') + end + end + + context 'when entity is project' do + let(:entity) { create(:bulk_import_entity, :project_entity) } + + it 'queries project & project members' do + expect(query.to_s).to include('project') + expect(query.to_s).to include('projectMembers') + end + end + end +end diff --git a/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb new file mode 100644 index 00000000000..b769aa4af5a --- /dev/null +++ b/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb @@ -0,0 +1,210 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline do + let_it_be(:portable) { create(:project) } + let_it_be(:oid) { 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' } + + let(:tmpdir) { Dir.mktmpdir } + let(:entity) { create(:bulk_import_entity, :project_entity, project: portable, source_full_path: 'test') } + let(:tracker) { create(:bulk_import_tracker, entity: entity) } + let(:context) { BulkImports::Pipeline::Context.new(tracker) } + let(:lfs_dir_path) { tmpdir } + let(:lfs_json_file_path) { File.join(lfs_dir_path, 'lfs_objects.json')} + let(:lfs_file_path) { File.join(lfs_dir_path, oid)} + + subject(:pipeline) { described_class.new(context) } + + before do + FileUtils.mkdir_p(lfs_dir_path) + FileUtils.touch(lfs_json_file_path) + FileUtils.touch(lfs_file_path) + File.write(lfs_json_file_path, { oid => [0, 1, 2, nil] }.to_json ) + + allow(Dir).to receive(:mktmpdir).with('bulk_imports').and_return(tmpdir) + end + + after do + FileUtils.remove_entry(tmpdir) if Dir.exist?(tmpdir) + end + + describe '#run' do + it 'imports lfs objects into destination project and removes tmpdir' do + allow(pipeline) + .to receive(:extract) + .and_return(BulkImports::Pipeline::ExtractedData.new(data: [lfs_json_file_path, lfs_file_path])) + + pipeline.run + + expect(portable.lfs_objects.count).to eq(1) + expect(portable.lfs_objects_projects.count).to eq(4) + expect(Dir.exist?(tmpdir)).to eq(false) + end + end + + describe '#extract' do + it 'downloads & extracts lfs objects filepaths' do + download_service = instance_double("BulkImports::FileDownloadService") + decompression_service = instance_double("BulkImports::FileDecompressionService") + extraction_service = instance_double("BulkImports::ArchiveExtractionService") + + expect(BulkImports::FileDownloadService) + .to receive(:new) + .with( + configuration: context.configuration, + relative_url: "/#{entity.pluralized_name}/test/export_relations/download?relation=lfs_objects", + tmpdir: tmpdir, + filename: 'lfs_objects.tar.gz') + .and_return(download_service) + expect(BulkImports::FileDecompressionService).to receive(:new).with(tmpdir: tmpdir, filename: 'lfs_objects.tar.gz').and_return(decompression_service) + expect(BulkImports::ArchiveExtractionService).to receive(:new).with(tmpdir: tmpdir, filename: 'lfs_objects.tar').and_return(extraction_service) + + expect(download_service).to receive(:execute) + expect(decompression_service).to receive(:execute) + expect(extraction_service).to receive(:execute) + + extracted_data = pipeline.extract(context) + + expect(extracted_data.data).to contain_exactly(lfs_json_file_path, lfs_file_path) + end + end + + describe '#load' do + before do + allow(pipeline) + .to receive(:extract) + .and_return(BulkImports::Pipeline::ExtractedData.new(data: [lfs_json_file_path, lfs_file_path])) + end + + context 'when file path is lfs json' do + it 'returns' do + filepath = File.join(tmpdir, 'lfs_objects.json') + + allow(Gitlab::Json).to receive(:parse).with(filepath).and_return({}) + + expect { pipeline.load(context, filepath) }.not_to change { portable.lfs_objects.count } + end + end + + context 'when file path is tar file' do + it 'returns' do + filepath = File.join(tmpdir, 'lfs_objects.tar') + + expect { pipeline.load(context, filepath) }.not_to change { portable.lfs_objects.count } + end + end + + context 'when lfs json read failed' do + it 'raises an error' do + File.write(lfs_json_file_path, 'invalid json') + + expect { pipeline.load(context, lfs_file_path) }.to raise_error(BulkImports::Error, 'LFS Objects JSON read failed') + end + end + + context 'when file path is being traversed' do + it 'raises an error' do + expect { pipeline.load(context, File.join(tmpdir, '..')) }.to raise_error(Gitlab::Utils::PathTraversalAttackError, 'Invalid path') + end + end + + context 'when file path is not under tmpdir' do + it 'returns' do + expect { pipeline.load(context, '/home/test.txt') }.to raise_error(StandardError, 'path /home/test.txt is not allowed') + end + end + + context 'when file path is symlink' do + it 'returns' do + symlink = File.join(tmpdir, 'symlink') + + FileUtils.ln_s(File.join(tmpdir, lfs_file_path), symlink) + + expect { pipeline.load(context, symlink) }.not_to change { portable.lfs_objects.count } + end + end + + context 'when path is a directory' do + it 'returns' do + expect { pipeline.load(context, Dir.tmpdir) }.not_to change { portable.lfs_objects.count } + end + end + + context 'lfs objects project' do + context 'when lfs objects json is invalid' do + context 'when oid value is not Array' do + it 'does not create lfs objects project' do + File.write(lfs_json_file_path, { oid => 'test' }.to_json ) + + expect { pipeline.load(context, lfs_file_path) }.not_to change { portable.lfs_objects_projects.count } + end + end + + context 'when oid value is nil' do + it 'does not create lfs objects project' do + File.write(lfs_json_file_path, { oid => nil }.to_json ) + + expect { pipeline.load(context, lfs_file_path) }.not_to change { portable.lfs_objects_projects.count } + end + end + + context 'when oid value is not allowed' do + it 'does not create lfs objects project' do + File.write(lfs_json_file_path, { oid => ['invalid'] }.to_json ) + + expect { pipeline.load(context, lfs_file_path) }.not_to change { portable.lfs_objects_projects.count } + end + end + + context 'when repository type is duplicated' do + it 'creates only one lfs objects project' do + File.write(lfs_json_file_path, { oid => [0, 0, 1, 1, 2, 2] }.to_json ) + + expect { pipeline.load(context, lfs_file_path) }.to change { portable.lfs_objects_projects.count }.by(3) + end + end + end + + context 'when lfs objects project fails to be created' do + it 'logs the failure' do + allow_next_instance_of(LfsObjectsProject) do |object| + allow(object).to receive(:persisted?).and_return(false) + end + + expect_next_instance_of(Gitlab::Import::Logger) do |logger| + expect(logger) + .to receive(:warn) + .with(project_id: portable.id, + message: 'Failed to save lfs objects project', + errors: '', **Gitlab::ApplicationContext.current) + .exactly(4).times + end + + pipeline.load(context, lfs_file_path) + end + end + end + end + + describe '#after_run' do + it 'removes tmpdir' do + allow(FileUtils).to receive(:remove_entry).and_call_original + expect(FileUtils).to receive(:remove_entry).with(tmpdir).and_call_original + + pipeline.after_run(nil) + + expect(Dir.exist?(tmpdir)).to eq(false) + end + + context 'when tmpdir does not exist' do + it 'does not attempt to remove tmpdir' do + FileUtils.remove_entry(tmpdir) + + expect(FileUtils).not_to receive(:remove_entry).with(tmpdir) + + pipeline.after_run(nil) + end + end + end +end diff --git a/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb new file mode 100644 index 00000000000..f9b95f79104 --- /dev/null +++ b/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe BulkImports::Common::Pipelines::MembersPipeline do + let_it_be(:user) { create(:user) } + let_it_be(:bulk_import) { create(:bulk_import, user: user) } + let_it_be(:member_user1) { create(:user, email: 'email1@email.com') } + let_it_be(:member_user2) { create(:user, email: 'email2@email.com') } + let_it_be(:member_data) do + { + user_id: member_user1.id, + created_by_id: member_user2.id, + access_level: 30, + created_at: '2020-01-01T00:00:00Z', + updated_at: '2020-01-01T00:00:00Z', + expires_at: nil + } + end + + let(:parent) { create(:group) } + let(:tracker) { create(:bulk_import_tracker, entity: entity) } + let(:context) { BulkImports::Pipeline::Context.new(tracker) } + let(:members) { portable.members.map { |m| m.slice(:user_id, :access_level) } } + + subject(:pipeline) { described_class.new(context) } + + def extracted_data(email:, has_next_page: false) + data = { + 'created_at' => '2020-01-01T00:00:00Z', + 'updated_at' => '2020-01-02T00:00:00Z', + 'expires_at' => nil, + 'access_level' => { + 'integer_value' => 30 + }, + 'user' => { + 'public_email' => email + } + } + + page_info = { + 'has_next_page' => has_next_page, + 'next_page' => has_next_page ? 'cursor' : nil + } + + BulkImports::Pipeline::ExtractedData.new(data: data, page_info: page_info) + end + + shared_examples 'members import' do + before do + portable.members.delete_all + end + + describe '#run' do + it 'creates memberships for existing users' do + first_page = extracted_data(email: member_user1.email, has_next_page: true) + last_page = extracted_data(email: member_user2.email) + + allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor| + allow(extractor).to receive(:extract).and_return(first_page, last_page) + end + + expect { pipeline.run }.to change(portable.members, :count).by(2) + + expect(members).to contain_exactly( + { user_id: member_user1.id, access_level: 30 }, + { user_id: member_user2.id, access_level: 30 } + ) + end + end + + describe '#load' do + it 'creates new membership' do + expect { subject.load(context, member_data) }.to change(portable.members, :count).by(1) + + member = portable.members.find_by_user_id(member_user1.id) + + expect(member.user).to eq(member_user1) + expect(member.created_by).to eq(member_user2) + expect(member.access_level).to eq(30) + expect(member.created_at).to eq('2020-01-01T00:00:00Z') + expect(member.updated_at).to eq('2020-01-01T00:00:00Z') + expect(member.expires_at).to eq(nil) + end + + context 'when user_id is current user id' do + it 'does not create new membership' do + data = { user_id: user.id } + + expect { pipeline.load(context, data) }.not_to change(portable.members, :count) + end + end + + context 'when data is nil' do + it 'does not create new membership' do + expect { pipeline.load(context, nil) }.not_to change(portable.members, :count) + end + end + + context 'when user membership already exists with the same access level' do + it 'does not create new membership' do + portable.members.create!(member_data) + + expect { pipeline.load(context, member_data) }.not_to change(portable.members, :count) + end + end + + context 'when portable is in a parent group' do + let(:tracker) { create(:bulk_import_tracker, entity: entity_with_parent) } + + before do + parent.members.create!(member_data) + end + + context 'when the same membership exists in parent group' do + it 'does not create new membership' do + expect { pipeline.load(context, member_data) }.not_to change(portable_with_parent.members, :count) + end + end + + context 'when membership with higher access level exists in parent group' do + it 'creates new direct membership' do + data = member_data.merge(access_level: Gitlab::Access::MAINTAINER) + + expect { pipeline.load(context, data) }.to change(portable_with_parent.members, :count) + + member = portable_with_parent.members.find_by_user_id(member_user1.id) + + expect(member.access_level).to eq(Gitlab::Access::MAINTAINER) + end + end + + context 'when membership with lower access level exists in parent group' do + it 'does not create new membership' do + data = member_data.merge(access_level: Gitlab::Access::GUEST) + + expect { pipeline.load(context, data) }.not_to change(portable_with_parent.members, :count) + end + end + end + end + end + + context 'when importing to group' do + let(:portable) { create(:group) } + let(:portable_with_parent) { create(:group, parent: parent) } + let(:entity) { create(:bulk_import_entity, :group_entity, group: portable, bulk_import: bulk_import) } + let(:entity_with_parent) { create(:bulk_import_entity, :group_entity, group: portable_with_parent, bulk_import: bulk_import) } + + include_examples 'members import' + end + + context 'when importing to project' do + let(:portable) { create(:project) } + let(:portable_with_parent) { create(:project, namespace: parent) } + let(:entity) { create(:bulk_import_entity, :project_entity, project: portable, bulk_import: bulk_import) } + let(:entity_with_parent) { create(:bulk_import_entity, :project_entity, project: portable_with_parent, bulk_import: bulk_import) } + + include_examples 'members import' + end +end diff --git a/spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb b/spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb index b0f8f74783b..d03b8d8b5b2 100644 --- a/spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb +++ b/spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb @@ -3,14 +3,27 @@ require 'spec_helper' RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do + let_it_be(:tracker) { create(:bulk_import_tracker) } + let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } + + subject(:query) { described_class.new(context: context) } + + it 'has a valid query' do + parsed_query = GraphQL::Query.new( + GitlabSchema, + query.to_s, + variables: query.variables + ) + result = GitlabSchema.static_validator.validate(parsed_query) + + expect(result[:errors]).to be_empty + end + describe '#variables' do it 'returns query variables based on entity information' do - entity = double(source_full_path: 'test', bulk_import: nil) - tracker = double(entity: entity) - context = BulkImports::Pipeline::Context.new(tracker) - expected = { full_path: entity.source_full_path } + expected = { full_path: tracker.entity.source_full_path } - expect(described_class.variables(context)).to eq(expected) + expect(subject.variables).to eq(expected) end end @@ -18,7 +31,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do it 'returns data path' do expected = %w[data group] - expect(described_class.data_path).to eq(expected) + expect(subject.data_path).to eq(expected) end end @@ -26,7 +39,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do it 'returns pagination information path' do expected = %w[data group page_info] - expect(described_class.page_info_path).to eq(expected) + expect(subject.page_info_path).to eq(expected) end end end diff --git a/spec/lib/bulk_imports/groups/graphql/get_members_query_spec.rb b/spec/lib/bulk_imports/groups/graphql/get_members_query_spec.rb deleted file mode 100644 index d0c4bb817b2..00000000000 --- a/spec/lib/bulk_imports/groups/graphql/get_members_query_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe BulkImports::Groups::Graphql::GetMembersQuery do - it 'has a valid query' do - tracker = create(:bulk_import_tracker) - context = BulkImports::Pipeline::Context.new(tracker) - - query = GraphQL::Query.new( - GitlabSchema, - described_class.to_s, - variables: described_class.variables(context) - ) - result = GitlabSchema.static_validator.validate(query) - - expect(result[:errors]).to be_empty - end - - describe '#data_path' do - it 'returns data path' do - expected = %w[data group group_members nodes] - - expect(described_class.data_path).to eq(expected) - end - end - - describe '#page_info_path' do - it 'returns pagination information path' do - expected = %w[data group group_members page_info] - - expect(described_class.page_info_path).to eq(expected) - end - end -end diff --git a/spec/lib/bulk_imports/groups/graphql/get_projects_query_spec.rb b/spec/lib/bulk_imports/groups/graphql/get_projects_query_spec.rb index 1a7c5a4993c..fe28e3959a0 100644 --- a/spec/lib/bulk_imports/groups/graphql/get_projects_query_spec.rb +++ b/spec/lib/bulk_imports/groups/graphql/get_projects_query_spec.rb @@ -3,25 +3,25 @@ require 'spec_helper' RSpec.describe BulkImports::Groups::Graphql::GetProjectsQuery do - describe '#variables' do - it 'returns valid variables based on entity information' do - tracker = create(:bulk_import_tracker) - context = BulkImports::Pipeline::Context.new(tracker) - - query = GraphQL::Query.new( - GitlabSchema, - described_class.to_s, - variables: described_class.variables(context) - ) - result = GitlabSchema.static_validator.validate(query) - - expect(result[:errors]).to be_empty - end + let_it_be(:tracker) { create(:bulk_import_tracker) } + let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } + + subject(:query) { described_class.new(context: context) } + + it 'has a valid query' do + parsed_query = GraphQL::Query.new( + GitlabSchema, + query.to_s, + variables: query.variables + ) + result = GitlabSchema.static_validator.validate(parsed_query) + + expect(result[:errors]).to be_empty + end - context 'with invalid variables' do - it 'raises an error' do - expect { GraphQL::Query.new(GitlabSchema, described_class.to_s, variables: 'invalid') }.to raise_error(ArgumentError) - end + context 'with invalid variables' do + it 'raises an error' do + expect { GraphQL::Query.new(GitlabSchema, subject.to_s, variables: 'invalid') }.to raise_error(ArgumentError) end end @@ -29,7 +29,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetProjectsQuery do it 'returns data path' do expected = %w[data group projects nodes] - expect(described_class.data_path).to eq(expected) + expect(subject.data_path).to eq(expected) end end @@ -37,7 +37,7 @@ RSpec.describe BulkImports::Groups::Graphql::GetProjectsQuery do it 'returns pagination information path' do expected = %w[data group projects page_info] - expect(described_class.page_info_path).to eq(expected) + expect(subject.page_info_path).to eq(expected) end end end diff --git a/spec/lib/bulk_imports/groups/pipelines/members_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/members_pipeline_spec.rb deleted file mode 100644 index 0126acb320b..00000000000 --- a/spec/lib/bulk_imports/groups/pipelines/members_pipeline_spec.rb +++ /dev/null @@ -1,119 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe BulkImports::Groups::Pipelines::MembersPipeline do - let_it_be(:member_user1) { create(:user, email: 'email1@email.com') } - let_it_be(:member_user2) { create(:user, email: 'email2@email.com') } - - let_it_be(:user) { create(:user) } - let_it_be(:group) { create(:group) } - let_it_be(:bulk_import) { create(:bulk_import, user: user) } - let_it_be(:entity) { create(:bulk_import_entity, bulk_import: bulk_import, group: group) } - let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) } - let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } - - subject { described_class.new(context) } - - describe '#run' do - it 'maps existing users to the imported group' do - first_page = extracted_data(email: member_user1.email, has_next_page: true) - last_page = extracted_data(email: member_user2.email) - - allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor| - allow(extractor) - .to receive(:extract) - .and_return(first_page, last_page) - end - - expect { subject.run }.to change(GroupMember, :count).by(2) - - members = group.members.map { |m| m.slice(:user_id, :access_level) } - - expect(members).to contain_exactly( - { user_id: member_user1.id, access_level: 30 }, - { user_id: member_user2.id, access_level: 30 } - ) - end - end - - describe '#load' do - it 'does nothing when there is no data' do - expect { subject.load(context, nil) }.not_to change(GroupMember, :count) - end - - it 'creates the member' do - data = { - 'user_id' => member_user1.id, - 'created_by_id' => member_user2.id, - 'access_level' => 30, - 'created_at' => '2020-01-01T00:00:00Z', - 'updated_at' => '2020-01-01T00:00:00Z', - 'expires_at' => nil - } - - expect { subject.load(context, data) }.to change(GroupMember, :count).by(1) - - member = group.members.last - - expect(member.user).to eq(member_user1) - expect(member.created_by).to eq(member_user2) - expect(member.access_level).to eq(30) - expect(member.created_at).to eq('2020-01-01T00:00:00Z') - expect(member.updated_at).to eq('2020-01-01T00:00:00Z') - expect(member.expires_at).to eq(nil) - end - - context 'when user_id is current user id' do - it 'does not create new member' do - data = { 'user_id' => user.id } - - expect { subject.load(context, data) }.not_to change(GroupMember, :count) - end - end - end - - describe 'pipeline parts' do - it { expect(described_class).to include_module(BulkImports::Pipeline) } - it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) } - - it 'has extractors' do - expect(described_class.get_extractor) - .to eq( - klass: BulkImports::Common::Extractors::GraphqlExtractor, - options: { - query: BulkImports::Groups::Graphql::GetMembersQuery - } - ) - end - - it 'has transformers' do - expect(described_class.transformers) - .to contain_exactly( - { klass: BulkImports::Common::Transformers::ProhibitedAttributesTransformer, options: nil }, - { klass: BulkImports::Groups::Transformers::MemberAttributesTransformer, options: nil } - ) - end - end - - def extracted_data(email:, has_next_page: false) - data = { - 'created_at' => '2020-01-01T00:00:00Z', - 'updated_at' => '2020-01-01T00:00:00Z', - 'expires_at' => nil, - 'access_level' => { - 'integer_value' => 30 - }, - 'user' => { - 'public_email' => email - } - } - - page_info = { - 'has_next_page' => has_next_page, - 'next_page' => has_next_page ? 'cursor' : nil - } - - BulkImports::Pipeline::ExtractedData.new(data: data, page_info: page_info) - end -end diff --git a/spec/lib/bulk_imports/groups/stage_spec.rb b/spec/lib/bulk_imports/groups/stage_spec.rb index 55a8e40f480..b6bb8a7d195 100644 --- a/spec/lib/bulk_imports/groups/stage_spec.rb +++ b/spec/lib/bulk_imports/groups/stage_spec.rb @@ -9,7 +9,7 @@ RSpec.describe BulkImports::Groups::Stage do [ [0, BulkImports::Groups::Pipelines::GroupPipeline], [1, BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline], - [1, BulkImports::Groups::Pipelines::MembersPipeline], + [1, BulkImports::Common::Pipelines::MembersPipeline], [1, BulkImports::Common::Pipelines::LabelsPipeline], [1, BulkImports::Common::Pipelines::MilestonesPipeline], [1, BulkImports::Common::Pipelines::BadgesPipeline], diff --git a/spec/lib/bulk_imports/groups/transformers/member_attributes_transformer_spec.rb b/spec/lib/bulk_imports/groups/transformers/member_attributes_transformer_spec.rb index af99428e0c1..c8935f71f10 100644 --- a/spec/lib/bulk_imports/groups/transformers/member_attributes_transformer_spec.rb +++ b/spec/lib/bulk_imports/groups/transformers/member_attributes_transformer_spec.rb @@ -48,12 +48,12 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do data = member_data(email: user.email) expect(subject.transform(context, data)).to eq( - 'access_level' => 30, - 'user_id' => user.id, - 'created_by_id' => user.id, - 'created_at' => '2020-01-01T00:00:00Z', - 'updated_at' => '2020-01-01T00:00:00Z', - 'expires_at' => nil + access_level: 30, + user_id: user.id, + created_by_id: user.id, + created_at: '2020-01-01T00:00:00Z', + updated_at: '2020-01-01T00:00:00Z', + expires_at: nil ) end @@ -62,12 +62,12 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do data = member_data(email: secondary_email) expect(subject.transform(context, data)).to eq( - 'access_level' => 30, - 'user_id' => user.id, - 'created_by_id' => user.id, - 'created_at' => '2020-01-01T00:00:00Z', - 'updated_at' => '2020-01-01T00:00:00Z', - 'expires_at' => nil + access_level: 30, + user_id: user.id, + created_by_id: user.id, + created_at: '2020-01-01T00:00:00Z', + updated_at: '2020-01-01T00:00:00Z', + expires_at: nil ) end diff --git a/spec/lib/bulk_imports/projects/graphql/get_project_query_spec.rb b/spec/lib/bulk_imports/projects/graphql/get_project_query_spec.rb new file mode 100644 index 00000000000..6593aa56506 --- /dev/null +++ b/spec/lib/bulk_imports/projects/graphql/get_project_query_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe BulkImports::Projects::Graphql::GetProjectQuery do + let_it_be(:tracker) { create(:bulk_import_tracker) } + let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } + + subject(:query) { described_class.new(context: context) } + + it 'has a valid query' do + parsed_query = GraphQL::Query.new( + GitlabSchema, + query.to_s, + variables: query.variables + ) + result = GitlabSchema.static_validator.validate(parsed_query) + + expect(result[:errors]).to be_empty + end + + it 'queries project based on source_full_path' do + expected = { full_path: tracker.entity.source_full_path } + + expect(subject.variables).to eq(expected) + end +end diff --git a/spec/lib/bulk_imports/projects/graphql/get_repository_query_spec.rb b/spec/lib/bulk_imports/projects/graphql/get_repository_query_spec.rb index 4dba81dc0d2..8ed105bc0c9 100644 --- a/spec/lib/bulk_imports/projects/graphql/get_repository_query_spec.rb +++ b/spec/lib/bulk_imports/projects/graphql/get_repository_query_spec.rb @@ -3,19 +3,29 @@ require 'spec_helper' RSpec.describe BulkImports::Projects::Graphql::GetRepositoryQuery do - describe 'query repository based on full_path' do - let(:entity) { double(source_full_path: 'test', bulk_import: nil) } - let(:tracker) { double(entity: entity) } - let(:context) { BulkImports::Pipeline::Context.new(tracker) } + let_it_be(:tracker) { create(:bulk_import_tracker) } + let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } - it 'returns project repository url' do - expect(described_class.to_s).to include('httpUrlToRepo') - end + subject(:query) { described_class.new(context: context) } - it 'queries project based on source_full_path' do - expected = { full_path: entity.source_full_path } + it 'has a valid query' do + parsed_query = GraphQL::Query.new( + GitlabSchema, + query.to_s, + variables: query.variables + ) + result = GitlabSchema.static_validator.validate(parsed_query) - expect(described_class.variables(context)).to eq(expected) - end + expect(result[:errors]).to be_empty + end + + it 'returns project repository url' do + expect(subject.to_s).to include('httpUrlToRepo') + end + + it 'queries project based on source_full_path' do + expected = { full_path: tracker.entity.source_full_path } + + expect(subject.variables).to eq(expected) end end diff --git a/spec/lib/bulk_imports/projects/graphql/get_snippet_repository_query_spec.rb b/spec/lib/bulk_imports/projects/graphql/get_snippet_repository_query_spec.rb index b680fa5cbfc..1bd4106297d 100644 --- a/spec/lib/bulk_imports/projects/graphql/get_snippet_repository_query_spec.rb +++ b/spec/lib/bulk_imports/projects/graphql/get_snippet_repository_query_spec.rb @@ -3,56 +3,56 @@ require 'spec_helper' RSpec.describe BulkImports::Projects::Graphql::GetSnippetRepositoryQuery do - describe 'query repository based on full_path' do - let_it_be(:entity) { create(:bulk_import_entity) } - let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) } - let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } - - it 'has a valid query' do - query = GraphQL::Query.new( - GitlabSchema, - described_class.to_s, - variables: described_class.variables(context) - ) - result = GitlabSchema.static_validator.validate(query) - - expect(result[:errors]).to be_empty - end + let_it_be(:entity) { create(:bulk_import_entity) } + let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) } + let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } - it 'returns snippet httpUrlToRepo' do - expect(described_class.to_s).to include('httpUrlToRepo') - end + subject(:query) { described_class.new(context: context) } - it 'returns snippet createdAt' do - expect(described_class.to_s).to include('createdAt') - end + it 'has a valid query' do + parsed_query = GraphQL::Query.new( + GitlabSchema, + query.to_s, + variables: query.variables + ) + result = GitlabSchema.static_validator.validate(parsed_query) - it 'returns snippet title' do - expect(described_class.to_s).to include('title') - end + expect(result[:errors]).to be_empty + end - describe '.variables' do - it 'queries project based on source_full_path and pagination' do - expected = { full_path: entity.source_full_path, cursor: nil, per_page: 500 } + it 'returns snippet httpUrlToRepo' do + expect(subject.to_s).to include('httpUrlToRepo') + end - expect(described_class.variables(context)).to eq(expected) - end + it 'returns snippet createdAt' do + expect(subject.to_s).to include('createdAt') + end + + it 'returns snippet title' do + expect(subject.to_s).to include('title') + end + + describe '.variables' do + it 'queries project based on source_full_path and pagination' do + expected = { full_path: entity.source_full_path, cursor: nil, per_page: 500 } + + expect(subject.variables).to eq(expected) end + end - describe '.data_path' do - it '.data_path returns data path' do - expected = %w[data project snippets nodes] + describe '.data_path' do + it '.data_path returns data path' do + expected = %w[data project snippets nodes] - expect(described_class.data_path).to eq(expected) - end + expect(subject.data_path).to eq(expected) end + end - describe '.page_info_path' do - it '.page_info_path returns pagination information path' do - expected = %w[data project snippets page_info] + describe '.page_info_path' do + it '.page_info_path returns pagination information path' do + expected = %w[data project snippets page_info] - expect(described_class.page_info_path).to eq(expected) - end + expect(subject.page_info_path).to eq(expected) end end end diff --git a/spec/lib/bulk_imports/projects/stage_spec.rb b/spec/lib/bulk_imports/projects/stage_spec.rb index 81cbdcae9d1..ef98613dc25 100644 --- a/spec/lib/bulk_imports/projects/stage_spec.rb +++ b/spec/lib/bulk_imports/projects/stage_spec.rb @@ -26,6 +26,7 @@ RSpec.describe BulkImports::Projects::Stage do [4, BulkImports::Projects::Pipelines::ServiceDeskSettingPipeline], [5, BulkImports::Common::Pipelines::WikiPipeline], [5, BulkImports::Common::Pipelines::UploadsPipeline], + [5, BulkImports::Common::Pipelines::LfsObjectsPipeline], [5, BulkImports::Projects::Pipelines::AutoDevopsPipeline], [5, BulkImports::Projects::Pipelines::PipelineSchedulesPipeline], [6, BulkImports::Common::Pipelines::EntityFinisher] diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb index 259d7d5ad13..974c3478ddc 100644 --- a/spec/lib/container_registry/client_spec.rb +++ b/spec/lib/container_registry/client_spec.rb @@ -5,29 +5,7 @@ require 'spec_helper' RSpec.describe ContainerRegistry::Client do using RSpec::Parameterized::TableSyntax - let(:token) { '12345' } - let(:options) { { token: token } } - let(:registry_api_url) { 'http://container-registry' } - let(:client) { described_class.new(registry_api_url, options) } - let(:push_blob_headers) do - { - 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json', - 'Authorization' => "bearer #{token}", - 'Content-Type' => 'application/octet-stream', - 'User-Agent' => "GitLab/#{Gitlab::VERSION}" - } - end - - let(:headers_with_accept_types) do - { - 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json', - 'Authorization' => "bearer #{token}", - 'User-Agent' => "GitLab/#{Gitlab::VERSION}" - } - end - - let(:expected_faraday_headers) { { user_agent: "GitLab/#{Gitlab::VERSION}" } } - let(:expected_faraday_request_options) { Gitlab::HTTP::DEFAULT_TIMEOUT_OPTIONS } + include_context 'container registry client' shared_examples 'handling timeouts' do let(:retry_options) do @@ -48,14 +26,14 @@ RSpec.describe ContainerRegistry::Client do retry_block: -> (_, _, _, _) { actual_retries += 1 } ) - stub_const('ContainerRegistry::Client::RETRY_OPTIONS', retry_options_with_block) + stub_const('ContainerRegistry::BaseClient::RETRY_OPTIONS', retry_options_with_block) expect { subject }.to raise_error(Faraday::ConnectionFailed) expect(actual_retries).to eq(retry_options_with_block[:max]) end it 'logs the error' do - stub_const('ContainerRegistry::Client::RETRY_OPTIONS', retry_options) + stub_const('ContainerRegistry::BaseClient::RETRY_OPTIONS', retry_options) expect(Gitlab::ErrorTracking) .to receive(:log_exception) @@ -63,7 +41,7 @@ RSpec.describe ContainerRegistry::Client do .times .with( an_instance_of(Faraday::ConnectionFailed), - class: described_class.name, + class: ::ContainerRegistry::BaseClient.name, url: URI(url) ) @@ -325,14 +303,14 @@ RSpec.describe ContainerRegistry::Client do subject { client.supports_tag_delete? } where(:registry_tags_support_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do - true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true - true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | true - true | true | [] | true | true - true | false | [] | true | true - false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true - false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | false - false | true | [] | true | false - false | false | [] | true | false + true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true + true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | true + true | true | [] | true | true + true | false | [] | true | true + false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true + false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | false + false | true | [] | true | false + false | false | [] | true | false end with_them do @@ -366,38 +344,38 @@ RSpec.describe ContainerRegistry::Client do subject { described_class.supports_tag_delete? } where(:registry_api_url, :registry_enabled, :registry_tags_support_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do - 'http://sandbox.local' | true | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true - 'http://sandbox.local' | true | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | true - 'http://sandbox.local' | true | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true - 'http://sandbox.local' | true | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | false - 'http://sandbox.local' | false | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - 'http://sandbox.local' | false | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - 'http://sandbox.local' | false | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - 'http://sandbox.local' | false | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - 'http://sandbox.local' | true | true | true | [] | true | true - 'http://sandbox.local' | true | true | false | [] | true | true - 'http://sandbox.local' | true | false | true | [] | true | false - 'http://sandbox.local' | true | false | false | [] | true | false - 'http://sandbox.local' | false | true | true | [] | false | false - 'http://sandbox.local' | false | true | false | [] | false | false - 'http://sandbox.local' | false | false | true | [] | false | false - 'http://sandbox.local' | false | false | false | [] | false | false - '' | true | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - '' | true | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - '' | true | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - '' | true | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - '' | false | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - '' | false | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - '' | false | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - '' | false | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false - '' | true | true | true | [] | false | false - '' | true | true | false | [] | false | false - '' | true | false | true | [] | false | false - '' | true | false | false | [] | false | false - '' | false | true | true | [] | false | false - '' | false | true | false | [] | false | false - '' | false | false | true | [] | false | false - '' | false | false | false | [] | false | false + 'http://sandbox.local' | true | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true + 'http://sandbox.local' | true | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | true + 'http://sandbox.local' | true | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true + 'http://sandbox.local' | true | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | false + 'http://sandbox.local' | false | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + 'http://sandbox.local' | false | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + 'http://sandbox.local' | false | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + 'http://sandbox.local' | false | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + 'http://sandbox.local' | true | true | true | [] | true | true + 'http://sandbox.local' | true | true | false | [] | true | true + 'http://sandbox.local' | true | false | true | [] | true | false + 'http://sandbox.local' | true | false | false | [] | true | false + 'http://sandbox.local' | false | true | true | [] | false | false + 'http://sandbox.local' | false | true | false | [] | false | false + 'http://sandbox.local' | false | false | true | [] | false | false + 'http://sandbox.local' | false | false | false | [] | false | false + '' | true | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + '' | true | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + '' | true | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + '' | true | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + '' | false | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + '' | false | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + '' | false | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + '' | false | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false + '' | true | true | true | [] | false | false + '' | true | true | false | [] | false | false + '' | true | false | true | [] | false | false + '' | true | false | false | [] | false | false + '' | false | true | true | [] | false | false + '' | false | true | false | [] | false | false + '' | false | false | true | [] | false | false + '' | false | false | false | [] | false | false end with_them do diff --git a/spec/lib/container_registry/gitlab_api_client_spec.rb b/spec/lib/container_registry/gitlab_api_client_spec.rb new file mode 100644 index 00000000000..292582a8d83 --- /dev/null +++ b/spec/lib/container_registry/gitlab_api_client_spec.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ContainerRegistry::GitlabApiClient do + using RSpec::Parameterized::TableSyntax + + include_context 'container registry client' + + let(:path) { 'namespace/path/to/repository' } + + describe '#supports_gitlab_api?' do + subject { client.supports_gitlab_api? } + + where(:registry_gitlab_api_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do + false | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true + true | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | true + true | true | [] | true | true + true | false | [] | true | true + false | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true + false | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | false + false | true | [] | true | false + false | false | [] | true | false + end + + with_them do + before do + allow(::Gitlab).to receive(:com?).and_return(is_on_dot_com) + stub_registry_gitlab_api_support(registry_gitlab_api_enabled) + stub_application_setting(container_registry_features: container_registry_features) + end + + it 'returns the expected result' do + if expect_registry_to_be_pinged + expect(Faraday::Connection).to receive(:new).and_call_original + else + expect(Faraday::Connection).not_to receive(:new) + end + + expect(subject).to be expected_result + end + end + + context 'with 401 response' do + before do + allow(::Gitlab).to receive(:com?).and_return(false) + stub_application_setting(container_registry_features: []) + stub_request(:get, "#{registry_api_url}/gitlab/v1/") + .to_return(status: 401, body: '') + end + + it { is_expected.to be_truthy } + end + end + + describe '#pre_import_repository' do + subject { client.pre_import_repository(path) } + + where(:status_code, :expected_result) do + 200 | :already_imported + 202 | :ok + 401 | :unauthorized + 404 | :not_found + 409 | :already_being_imported + 418 | :error + 424 | :pre_import_failed + 425 | :already_being_imported + 429 | :too_many_imports + end + + with_them do + before do + stub_pre_import(path, status_code, pre: true) + end + + it { is_expected.to eq(expected_result) } + end + end + + describe '#import_repository' do + subject { client.import_repository(path) } + + where(:status_code, :expected_result) do + 200 | :already_imported + 202 | :ok + 401 | :unauthorized + 404 | :not_found + 409 | :already_being_imported + 418 | :error + 424 | :pre_import_failed + 425 | :already_being_imported + 429 | :too_many_imports + end + + with_them do + before do + stub_pre_import(path, status_code, pre: false) + end + + it { is_expected.to eq(expected_result) } + end + end + + describe '#import_status' do + subject { client.import_status(path) } + + before do + stub_import_status(path, status) + end + + context 'with a status' do + let(:status) { 'this_is_a_test' } + + it { is_expected.to eq(status) } + end + + context 'with no status' do + let(:status) { nil } + + it { is_expected.to eq('error') } + end + end + + describe '.supports_gitlab_api?' do + subject { described_class.supports_gitlab_api? } + + where(:registry_gitlab_api_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do + true | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true + true | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | true + false | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true + false | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | false + true | true | [] | true | true + true | false | [] | true | true + false | true | [] | true | false + false | false | [] | true | false + end + + with_them do + before do + allow(::Gitlab).to receive(:com?).and_return(is_on_dot_com) + stub_container_registry_config(enabled: true, api_url: registry_api_url, key: 'spec/fixtures/x509_certificate_pk.key') + stub_registry_gitlab_api_support(registry_gitlab_api_enabled) + stub_application_setting(container_registry_features: container_registry_features) + end + + it 'returns the expected result' do + if expect_registry_to_be_pinged + expect(Faraday::Connection).to receive(:new).and_call_original + else + expect(Faraday::Connection).not_to receive(:new) + end + + expect(subject).to be expected_result + end + end + + context 'with the registry disabled' do + before do + stub_container_registry_config(enabled: false, api_url: 'http://sandbox.local', key: 'spec/fixtures/x509_certificate_pk.key') + end + + it 'returns false' do + expect(Faraday::Connection).not_to receive(:new) + + expect(subject).to be_falsey + end + end + + context 'with a blank registry url' do + before do + stub_container_registry_config(enabled: true, api_url: '', key: 'spec/fixtures/x509_certificate_pk.key') + end + + it 'returns false' do + expect(Faraday::Connection).not_to receive(:new) + + expect(subject).to be_falsey + end + end + end + + def stub_pre_import(path, status_code, pre:) + stub_request(:put, "#{registry_api_url}/gitlab/v1/import/#{path}/?pre=#{pre}") + .with(headers: { 'Accept' => described_class::JSON_TYPE }) + .to_return(status: status_code, body: '') + end + + def stub_registry_gitlab_api_support(supported = true) + status_code = supported ? 200 : 404 + stub_request(:get, "#{registry_api_url}/gitlab/v1/") + .with(headers: { 'Accept' => described_class::JSON_TYPE }) + .to_return(status: status_code, body: '') + end + + def stub_import_status(path, status) + stub_request(:get, "#{registry_api_url}/gitlab/v1/import/#{path}/") + .with(headers: { 'Accept' => described_class::JSON_TYPE }) + .to_return( + status: 200, + body: { status: status }.to_json, + headers: { content_type: 'application/json' } + ) + end +end diff --git a/spec/lib/container_registry/migration_spec.rb b/spec/lib/container_registry/migration_spec.rb new file mode 100644 index 00000000000..ffbbfb249e3 --- /dev/null +++ b/spec/lib/container_registry/migration_spec.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ContainerRegistry::Migration do + using RSpec::Parameterized::TableSyntax + + describe '.enabled?' do + subject { described_class.enabled? } + + it { is_expected.to eq(true) } + + context 'feature flag disabled' do + before do + stub_feature_flags(container_registry_migration_phase2_enabled: false) + end + + it { is_expected.to eq(false) } + end + end + + describe '.limit_gitlab_org?' do + subject { described_class.limit_gitlab_org? } + + it { is_expected.to eq(true) } + + context 'feature flag disabled' do + before do + stub_feature_flags(container_registry_migration_limit_gitlab_org: false) + end + + it { is_expected.to eq(false) } + end + end + + describe '.enqueue_waiting_time' do + subject { described_class.enqueue_waiting_time } + + where(:slow_enabled, :fast_enabled, :expected_result) do + false | false | 1.hour + true | false | 6.hours + false | true | 0 + true | true | 0 + end + + with_them do + before do + stub_feature_flags( + container_registry_migration_phase2_enqueue_speed_slow: slow_enabled, + container_registry_migration_phase2_enqueue_speed_fast: fast_enabled + ) + end + + it { is_expected.to eq(expected_result) } + end + end + + describe '.capacity' do + subject { described_class.capacity } + + where(:ff_1_enabled, :ff_10_enabled, :ff_25_enabled, :expected_result) do + false | false | false | 0 + true | false | false | 1 + true | true | false | 10 + true | true | true | 25 + false | true | false | 10 + false | true | true | 25 + false | false | true | 25 + true | false | true | 25 + end + + with_them do + before do + stub_feature_flags( + container_registry_migration_phase2_capacity_1: ff_1_enabled, + container_registry_migration_phase2_capacity_10: ff_10_enabled, + container_registry_migration_phase2_capacity_25: ff_25_enabled + ) + end + + it { is_expected.to eq(expected_result) } + end + end + + describe '.max_tags_count' do + let(:value) { 1 } + + before do + stub_application_setting(container_registry_import_max_tags_count: value) + end + + it 'returns the matching application_setting' do + expect(described_class.max_tags_count).to eq(value) + end + end + + describe '.max_retries' do + let(:value) { 1 } + + before do + stub_application_setting(container_registry_import_max_retries: value) + end + + it 'returns the matching application_setting' do + expect(described_class.max_retries).to eq(value) + end + end + + describe '.start_max_retries' do + let(:value) { 1 } + + before do + stub_application_setting(container_registry_import_start_max_retries: value) + end + + it 'returns the matching application_setting' do + expect(described_class.start_max_retries).to eq(value) + end + end + + describe '.max_step_duration' do + let(:value) { 5.minutes } + + before do + stub_application_setting(container_registry_import_max_step_duration: value) + end + + it 'returns the matching application_setting' do + expect(described_class.max_step_duration).to eq(value) + end + end + + describe '.target_plan_name' do + let(:value) { 'free' } + + before do + stub_application_setting(container_registry_import_target_plan: value) + end + + it 'returns the matching application_setting' do + expect(described_class.target_plan_name).to eq(value) + end + end + + describe '.created_before' do + let(:value) { 1.day.ago } + + before do + stub_application_setting(container_registry_import_created_before: value) + end + + it 'returns the matching application_setting' do + expect(described_class.created_before).to eq(value) + end + end + + describe '.target_plan' do + let_it_be(:plan) { create(:plan) } + + before do + stub_application_setting(container_registry_import_target_plan: plan.name) + end + + it 'returns the matching application_setting' do + expect(described_class.target_plan).to eq(plan) + end + end +end diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb index d6e2b17f53b..c690d96b4f5 100644 --- a/spec/lib/container_registry/registry_spec.rb +++ b/spec/lib/container_registry/registry_spec.rb @@ -27,4 +27,14 @@ RSpec.describe ContainerRegistry::Registry do it { is_expected.to eq(path) } end end + + describe '#gitlab_api_client' do + subject { registry.gitlab_api_client } + + it 'returns a GitLabApiClient with an import token' do + expect(Auth::ContainerRegistryAuthenticationService).to receive(:import_access_token) + + expect(subject).to be_instance_of(ContainerRegistry::GitlabApiClient) + end + end end diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 9b2bb024fa6..5db2fbd923e 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -32,7 +32,7 @@ RSpec.describe ExtractsPath do describe '#assign_ref_vars' do let(:ref) { sample_commit[:id] } let(:path) { sample_commit[:line_code_path] } - let(:params) { { path: path, ref: ref } } + let(:params) { ActionController::Parameters.new(path: path, ref: ref) } it_behaves_like 'assigns ref vars' @@ -54,7 +54,8 @@ RSpec.describe ExtractsPath do context 'ref only exists without .atom suffix' do context 'with a path' do - let(:params) { { ref: 'v1.0.0.atom', path: 'README.md' } } + let(:ref) { 'v1.0.0.atom' } + let(:path) { 'README.md' } it 'renders a 404' do expect(self).to receive(:render_404) @@ -64,7 +65,8 @@ RSpec.describe ExtractsPath do end context 'without a path' do - let(:params) { { ref: 'v1.0.0.atom' } } + let(:ref) { 'v1.0.0.atom' } + let(:path) { nil } before do assign_ref_vars @@ -82,7 +84,8 @@ RSpec.describe ExtractsPath do context 'ref exists with .atom suffix' do context 'with a path' do - let(:params) { { ref: 'master.atom', path: 'README.md' } } + let(:ref) { 'master.atom' } + let(:path) { 'README.md' } before do repository = @project.repository @@ -102,7 +105,8 @@ RSpec.describe ExtractsPath do end context 'without a path' do - let(:params) { { ref: 'master.atom' } } + let(:ref) { 'master.atom' } + let(:path) { nil } before do repository = @project.repository @@ -125,7 +129,8 @@ RSpec.describe ExtractsPath do end context 'ref and path are nil' do - let(:params) { { path: nil, ref: nil } } + let(:path) { nil } + let(:ref) { nil } it 'does not set commit' do expect(container.repository).not_to receive(:commit).with('') @@ -209,8 +214,8 @@ RSpec.describe ExtractsPath do expect(extract_ref_without_atom('release/app/v1.0.0.atom')).to eq('release/app/v1.0.0') end - it 'returns nil if there are no matching refs' do - expect(extract_ref_without_atom('foo.atom')).to eq(nil) + it 'raises an error if there are no matching refs' do + expect { extract_ref_without_atom('foo.atom') }.to raise_error(ExtractsRef::InvalidPathError) end end end diff --git a/spec/lib/extracts_ref_spec.rb b/spec/lib/extracts_ref_spec.rb index 3cdce150de9..3e9a7499fdd 100644 --- a/spec/lib/extracts_ref_spec.rb +++ b/spec/lib/extracts_ref_spec.rb @@ -10,7 +10,8 @@ RSpec.describe ExtractsRef do let_it_be(:container) { create(:snippet, :repository, author: owner) } let(:ref) { sample_commit[:id] } - let(:params) { { path: sample_commit[:line_code_path], ref: ref } } + let(:path) { sample_commit[:line_code_path] } + let(:params) { ActionController::Parameters.new(path: path, ref: ref) } before do ref_names = ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0', 'release/app', 'release/app/v1.0.0'] @@ -23,7 +24,8 @@ RSpec.describe ExtractsRef do it_behaves_like 'assigns ref vars' context 'ref and path are nil' do - let(:params) { { path: nil, ref: nil } } + let(:ref) { nil } + let(:path) { nil } it 'does not set commit' do expect(container.repository).not_to receive(:commit).with('') @@ -33,6 +35,15 @@ RSpec.describe ExtractsRef do expect(@commit).to be_nil end end + + context 'when ref and path have incorrect format' do + let(:ref) { { wrong: :format } } + let(:path) { { also: :wrong } } + + it 'does not raise an exception' do + expect { assign_ref_vars }.not_to raise_error + end + end end it_behaves_like 'extracts refs' diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb index 8c546390201..5080d21d564 100644 --- a/spec/lib/feature_spec.rb +++ b/spec/lib/feature_spec.rb @@ -728,13 +728,13 @@ RSpec.describe Feature, stub_feature_flags: false do describe '#targets' do let(:project) { create(:project) } let(:group) { create(:group) } - let(:user_name) { project.owner.username } + let(:user_name) { project.first_owner.username } subject { described_class.new(user: user_name, project: project.full_path, group: group.full_path) } it 'returns all found targets' do expect(subject.targets).to be_an(Array) - expect(subject.targets).to eq([project.owner, project, group]) + expect(subject.targets).to eq([project.first_owner, project, group]) end end end diff --git a/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb b/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb index 4e172dd32f0..d9fa6b931ad 100644 --- a/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb +++ b/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do let(:ce_temp_dir) { Dir.mktmpdir } let(:ee_temp_dir) { Dir.mktmpdir } + let(:timestamp) { Time.current.to_i } let(:generator_options) { { 'category' => 'Groups::EmailCampaignsController', 'action' => 'click' } } before do @@ -12,6 +13,10 @@ RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do stub_const("#{described_class}::EE_DIR", ee_temp_dir) end + around do |example| + freeze_time { example.run } + end + after do FileUtils.rm_rf([ce_temp_dir, ee_temp_dir]) end @@ -22,16 +27,41 @@ RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do end let(:sample_event_dir) { 'lib/generators/gitlab/snowplow_event_definition_generator' } + let(:file_name) { Dir.children(ce_temp_dir).first } it 'creates CE event definition file using the template' do sample_event = ::Gitlab::Config::Loader::Yaml.new(fixture_file(File.join(sample_event_dir, 'sample_event.yml'))).load_raw! described_class.new([], generator_options).invoke_all - event_definition_path = File.join(ce_temp_dir, 'groups__email_campaigns_controller_click.yml') + event_definition_path = File.join(ce_temp_dir, file_name) expect(::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!).to eq(sample_event) end + describe 'generated filename' do + it 'includes timestamp' do + described_class.new([], generator_options).invoke_all + + expect(file_name).to include(timestamp.to_s) + end + + it 'removes special characters' do + generator_options = { 'category' => '"`ui:[mavenpackages | t5%348()-=@ ]`"', 'action' => 'click' } + + described_class.new([], generator_options).invoke_all + + expect(file_name).to include('uimavenpackagest') + end + + it 'cuts name if longer than 100 characters' do + generator_options = { 'category' => 'a' * 100, 'action' => 'click' } + + described_class.new([], generator_options).invoke_all + + expect(file_name.length).to eq(100) + end + end + context 'event definition already exists' do before do stub_const('Gitlab::VERSION', '12.11.0-pre') @@ -44,7 +74,7 @@ RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do stub_const('Gitlab::VERSION', '13.11.0-pre') described_class.new([], generator_options.merge('force' => true)).invoke_all - event_definition_path = File.join(ce_temp_dir, 'groups__email_campaigns_controller_click.yml') + event_definition_path = File.join(ce_temp_dir, file_name) event_data = ::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw! expect(event_data).to eq(sample_event) @@ -56,13 +86,17 @@ RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do end end - it 'creates EE event definition file using the template' do - sample_event = ::Gitlab::Config::Loader::Yaml.new(fixture_file(File.join(sample_event_dir, 'sample_event_ee.yml'))).load_raw! + describe 'EE' do + let(:file_name) { Dir.children(ee_temp_dir).first } - described_class.new([], generator_options.merge('ee' => true)).invoke_all + it 'creates EE event definition file using the template' do + sample_event = ::Gitlab::Config::Loader::Yaml.new(fixture_file(File.join(sample_event_dir, 'sample_event_ee.yml'))).load_raw! - event_definition_path = File.join(ee_temp_dir, 'groups__email_campaigns_controller_click.yml') - expect(::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!).to eq(sample_event) + described_class.new([], generator_options.merge('ee' => true)).invoke_all + + event_definition_path = File.join(ee_temp_dir, file_name) + expect(::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!).to eq(sample_event) + end end end end diff --git a/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb index 55ba6e56237..8eb75feaa8d 100644 --- a/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb +++ b/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb @@ -8,16 +8,17 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Aggregated::RecordsFetcher do let_it_be(:issue_2) { create(:issue, project: project) } let_it_be(:issue_3) { create(:issue, project: project) } - let_it_be(:stage_event_1) { create(:cycle_analytics_issue_stage_event, issue_id: issue_1.id, start_event_timestamp: 2.years.ago, end_event_timestamp: 1.year.ago) } # duration: 1 year - let_it_be(:stage_event_2) { create(:cycle_analytics_issue_stage_event, issue_id: issue_2.id, start_event_timestamp: 5.years.ago, end_event_timestamp: 2.years.ago) } # duration: 3 years - let_it_be(:stage_event_3) { create(:cycle_analytics_issue_stage_event, issue_id: issue_3.id, start_event_timestamp: 6.years.ago, end_event_timestamp: 3.months.ago) } # duration: 5+ years - let_it_be(:stage) { create(:cycle_analytics_project_stage, start_event_identifier: :issue_created, end_event_identifier: :issue_deployed_to_production, project: project) } - let(:params) { {} } + let_it_be(:stage_event_1) { create(:cycle_analytics_issue_stage_event, stage_event_hash_id: stage.stage_event_hash_id, project_id: project.id, issue_id: issue_1.id, start_event_timestamp: 2.years.ago, end_event_timestamp: 1.year.ago) } # duration: 1 year + let_it_be(:stage_event_2) { create(:cycle_analytics_issue_stage_event, stage_event_hash_id: stage.stage_event_hash_id, project_id: project.id, issue_id: issue_2.id, start_event_timestamp: 5.years.ago, end_event_timestamp: 2.years.ago) } # duration: 3 years + let_it_be(:stage_event_3) { create(:cycle_analytics_issue_stage_event, stage_event_hash_id: stage.stage_event_hash_id, project_id: project.id, issue_id: issue_3.id, start_event_timestamp: 6.years.ago, end_event_timestamp: 3.months.ago) } # duration: 5+ years + + let(:params) { { from: 10.years.ago, to: Date.today } } subject(:records_fetcher) do - described_class.new(stage: stage, query: Analytics::CycleAnalytics::IssueStageEvent.all, params: params) + query_builder = Gitlab::Analytics::CycleAnalytics::Aggregated::BaseQueryBuilder.new(stage: stage, params: params) + described_class.new(stage: stage, query: query_builder.build_sorted_query, params: params) end shared_examples 'match returned records' do @@ -123,6 +124,8 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Aggregated::RecordsFetcher do it 'skips non-existing issue records' do create(:cycle_analytics_issue_stage_event, { issue_id: 0, # non-existing id + stage_event_hash_id: stage.stage_event_hash_id, + project_id: project.id, start_event_timestamp: 5.months.ago, end_event_timestamp: 3.months.ago }) diff --git a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb index 4fe55ba0c0c..dc46dade87e 100644 --- a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb +++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb @@ -43,8 +43,8 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do end before do - issue1.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) - issue2.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) + issue1.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) + issue2.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) end context 'when records are loaded by guest' do @@ -73,8 +73,8 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do end before do - mr1.metrics.update(merged_at: 3.days.ago) - mr2.metrics.update(merged_at: 3.days.ago) + mr1.metrics.update!(merged_at: 3.days.ago) + mr2.metrics.update!(merged_at: 3.days.ago) end include_context 'when records are loaded by maintainer' @@ -95,9 +95,9 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do end before(:all) do - issue1.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) - issue2.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) - issue3.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) + issue1.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) + issue2.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) + issue3.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago) end before do diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb index 5ecec978017..55f5ae7d7dc 100644 --- a/spec/lib/gitlab/application_context_spec.rb +++ b/spec/lib/gitlab/application_context_spec.rb @@ -125,6 +125,17 @@ RSpec.describe Gitlab::ApplicationContext do .to include(project: project.full_path, root_namespace: project.full_path_components.first) end + it 'contains known keys' do + context = described_class.new(project: project) + + # Make sure all possible keys would be included + allow(context).to receive_message_chain(:set_values, :include?).and_return(true) + + # If a newly added key is added to the context hash, we need to list it in + # the known keys constant. This spec ensures that we do. + expect(context.to_lazy_hash.keys).to contain_exactly(*described_class.known_keys) + end + describe 'setting the client' do let_it_be(:remote_ip) { '127.0.0.1' } let_it_be(:runner) { create(:ci_runner) } diff --git a/spec/lib/gitlab/audit/ci_runner_token_author_spec.rb b/spec/lib/gitlab/audit/ci_runner_token_author_spec.rb new file mode 100644 index 00000000000..f55e1b44936 --- /dev/null +++ b/spec/lib/gitlab/audit/ci_runner_token_author_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Audit::CiRunnerTokenAuthor do + describe '.initialize' do + subject { described_class.new(audit_event) } + + let(:details) { } + let(:audit_event) { instance_double(AuditEvent, details: details, entity_type: 'Project', entity_path: 'd/e') } + + context 'with runner_authentication_token' do + let(:details) do + { runner_authentication_token: 'abc1234567' } + end + + it 'returns CiRunnerTokenAuthor with expected attributes' do + is_expected.to have_attributes(id: -1, name: 'Authentication token: abc1234567') + end + end + + context 'with runner_registration_token' do + let(:details) do + { runner_registration_token: 'abc1234567' } + end + + it 'returns CiRunnerTokenAuthor with expected attributes' do + is_expected.to have_attributes(id: -1, name: 'Registration token: abc1234567') + end + end + + context 'with runner token missing' do + let(:details) do + {} + end + + it 'raises ArgumentError' do + expect { subject }.to raise_error ArgumentError, 'Runner token missing' + end + end + end + + describe '#full_path' do + subject { author.full_path } + + let(:author) { described_class.new(audit_event) } + + context 'with instance registration token' do + let(:audit_event) { instance_double(AuditEvent, details: { runner_registration_token: 'abc1234567' }, entity_type: 'User', entity_path: nil) } + + it 'returns correct url' do + is_expected.to eq('/admin/runners') + end + end + + context 'with group registration token' do + let(:audit_event) { instance_double(AuditEvent, details: { runner_registration_token: 'abc1234567' }, entity_type: 'Group', entity_path: 'a/b') } + + it 'returns correct url' do + expect(::Gitlab::Routing.url_helpers).to receive(:group_settings_ci_cd_path) + .once + .with('a/b', { anchor: 'js-runners-settings' }) + .and_return('/path/to/group/runners') + + is_expected.to eq('/path/to/group/runners') + end + end + + context 'with project registration token' do + let(:audit_event) { instance_double(AuditEvent, details: { runner_registration_token: 'abc1234567' }, entity_type: 'Project', entity_path: project.full_path) } + let(:project) { create(:project) } + + it 'returns correct url' do + expect(::Gitlab::Routing.url_helpers).to receive(:project_settings_ci_cd_path) + .once + .with(project, { anchor: 'js-runners-settings' }) + .and_return('/path/to/project/runners') + + is_expected.to eq('/path/to/project/runners') + end + end + end +end diff --git a/spec/lib/gitlab/audit/null_author_spec.rb b/spec/lib/gitlab/audit/null_author_spec.rb index eb80e5faa89..7203a0cd816 100644 --- a/spec/lib/gitlab/audit/null_author_spec.rb +++ b/spec/lib/gitlab/audit/null_author_spec.rb @@ -6,13 +6,47 @@ RSpec.describe Gitlab::Audit::NullAuthor do subject { described_class } describe '.for' do + let(:audit_event) { instance_double(AuditEvent) } + it 'returns an DeletedAuthor' do - expect(subject.for(666, 'Old Hat')).to be_a(Gitlab::Audit::DeletedAuthor) + allow(audit_event).to receive(:[]).with(:author_name).and_return('Old Hat') + allow(audit_event).to receive(:details).and_return({}) + allow(audit_event).to receive(:target_type) + + expect(subject.for(666, audit_event)).to be_a(Gitlab::Audit::DeletedAuthor) end it 'returns an UnauthenticatedAuthor when id equals -1', :aggregate_failures do - expect(subject.for(-1, 'Frank')).to be_a(Gitlab::Audit::UnauthenticatedAuthor) - expect(subject.for(-1, 'Frank')).to have_attributes(id: -1, name: 'Frank') + allow(audit_event).to receive(:[]).with(:author_name).and_return('Frank') + allow(audit_event).to receive(:details).and_return({}) + allow(audit_event).to receive(:target_type) + + expect(subject.for(-1, audit_event)).to be_a(Gitlab::Audit::UnauthenticatedAuthor) + expect(subject.for(-1, audit_event)).to have_attributes(id: -1, name: 'Frank') + end + + it 'returns a CiRunnerTokenAuthor when details contain runner registration token', :aggregate_failures do + allow(audit_event).to receive(:[]).with(:author_name).and_return('cde456') + allow(audit_event).to receive(:entity_type).and_return('User') + allow(audit_event).to receive(:entity_path).and_return('/a/b') + allow(audit_event).to receive(:target_type).and_return(::Ci::Runner.name) + allow(audit_event).to receive(:details) + .and_return({ runner_registration_token: 'cde456', author_name: 'cde456', entity_type: 'User', entity_path: '/a/b' }) + + expect(subject.for(-1, audit_event)).to be_a(Gitlab::Audit::CiRunnerTokenAuthor) + expect(subject.for(-1, audit_event)).to have_attributes(id: -1, name: 'Registration token: cde456') + end + + it 'returns a CiRunnerTokenAuthor when details contain runner authentication token', :aggregate_failures do + allow(audit_event).to receive(:[]).with(:author_name).and_return('cde456') + allow(audit_event).to receive(:entity_type).and_return('User') + allow(audit_event).to receive(:entity_path).and_return('/a/b') + allow(audit_event).to receive(:target_type).and_return(::Ci::Runner.name) + allow(audit_event).to receive(:details) + .and_return({ runner_authentication_token: 'cde456', author_name: 'cde456', entity_type: 'User', entity_path: '/a/b' }) + + expect(subject.for(-1, audit_event)).to be_a(Gitlab::Audit::CiRunnerTokenAuthor) + expect(subject.for(-1, audit_event)).to have_attributes(id: -1, name: 'Authentication token: cde456') end end diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb index e910ac09448..da0bb5fe675 100644 --- a/spec/lib/gitlab/auth/ldap/user_spec.rb +++ b/spec/lib/gitlab/auth/ldap/user_spec.rb @@ -53,12 +53,12 @@ RSpec.describe Gitlab::Auth::Ldap::User do it "finds the user if already existing" do create(:omniauth_user, extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain') - expect { ldap_user.save }.not_to change { User.count } + expect { ldap_user.save }.not_to change { User.count } # rubocop:disable Rails/SaveBang end it "connects to existing non-ldap user if the email matches" do existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter") - expect { ldap_user.save }.not_to change { User.count } + expect { ldap_user.save }.not_to change { User.count } # rubocop:disable Rails/SaveBang existing_user.reload expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' @@ -67,7 +67,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do it 'connects to existing ldap user if the extern_uid changes' do existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain') - expect { ldap_user.save }.not_to change { User.count } + expect { ldap_user.save }.not_to change { User.count } # rubocop:disable Rails/SaveBang existing_user.reload expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' @@ -77,7 +77,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do it 'connects to existing ldap user if the extern_uid changes and email address has upper case characters' do existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain') - expect { ldap_user_upper_case.save }.not_to change { User.count } + expect { ldap_user_upper_case.save }.not_to change { User.count } # rubocop:disable Rails/SaveBang existing_user.reload expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' @@ -89,7 +89,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do existing_user = create(:omniauth_user, email: 'john@example.com', provider: 'twitter') expect(existing_user.identities.count).to be(1) - ldap_user.save + ldap_user.save # rubocop:disable Rails/SaveBang expect(ldap_user.gl_user.identities.count).to be(2) # Expect that find_by provider only returns a single instance of an identity and not an Enumerable @@ -98,7 +98,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do end it "creates a new user if not found" do - expect { ldap_user.save }.to change { User.count }.by(1) + expect { ldap_user.save }.to change { User.count }.by(1) # rubocop:disable Rails/SaveBang end context 'when signup is disabled' do @@ -107,7 +107,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do end it 'creates the user' do - ldap_user.save + ldap_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted end @@ -119,7 +119,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do end it 'creates and confirms the user anyway' do - ldap_user.save + ldap_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted expect(gl_user).to be_confirmed @@ -132,7 +132,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do end it 'creates the user' do - ldap_user.save + ldap_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted end @@ -189,7 +189,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do end it do - ldap_user.save + ldap_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -201,7 +201,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do end it do - ldap_user.save + ldap_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).to be_blocked end @@ -210,7 +210,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do context 'sign-in' do before do - ldap_user.save + ldap_user.save # rubocop:disable Rails/SaveBang ldap_user.gl_user.activate end @@ -220,7 +220,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do end it do - ldap_user.save + ldap_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -232,7 +232,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do end it do - ldap_user.save + ldap_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb index 7a8e6e77d52..8d36507ec7a 100644 --- a/spec/lib/gitlab/auth/o_auth/user_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb @@ -67,7 +67,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do create(:omniauth_user, extern_uid: 'my-uid', provider: provider) stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_falsey @@ -83,7 +83,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do it 'creates the user' do stub_omniauth_config(allow_single_sign_on: [provider]) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted end @@ -97,7 +97,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do it 'creates and confirms the user anyway' do stub_omniauth_config(allow_single_sign_on: [provider]) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted expect(gl_user).to be_confirmed @@ -112,7 +112,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do it 'creates the user' do stub_omniauth_config(allow_single_sign_on: [provider]) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted end @@ -121,7 +121,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do it 'marks user as having password_automatically_set' do stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted expect(gl_user).to be_password_automatically_set @@ -131,7 +131,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do context 'provider is marked as external' do it 'marks user as external' do stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_truthy end @@ -141,7 +141,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do it 'does not mark external user as internal' do create(:omniauth_user, extern_uid: 'my-uid', provider: provider, external: true) stub_omniauth_config(allow_single_sign_on: [provider], external_providers: ['facebook']) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_truthy end @@ -151,9 +151,9 @@ RSpec.describe Gitlab::Auth::OAuth::User do context 'when adding a new OAuth identity' do it 'does not promote an external user to internal' do user = create(:user, email: 'john@mail.com', external: true) - user.identities.create(provider: provider, extern_uid: uid) + user.identities.create!(provider: provider, extern_uid: uid) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_truthy end @@ -166,7 +166,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it "creates a user from Omniauth" do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid identity = gl_user.identities.first @@ -181,7 +181,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it "creates a user from Omniauth" do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid identity = gl_user.identities.first @@ -196,7 +196,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it 'throws an error' do - expect { oauth_user.save }.to raise_error StandardError + expect { oauth_user.save }.to raise_error StandardError # rubocop:disable Rails/SaveBang end end @@ -206,7 +206,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it 'throws an error' do - expect { oauth_user.save }.to raise_error StandardError + expect { oauth_user.save }.to raise_error StandardError # rubocop:disable Rails/SaveBang end end end @@ -228,7 +228,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } it "adds the OmniAuth identity to the GitLab user account" do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).not_to be_valid end @@ -248,7 +248,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } it "adds the OmniAuth identity to the GitLab user account" do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.username).to eql 'john' @@ -277,7 +277,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } it "adds the OmniAuth identity to the GitLab user account" do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.username).to eql 'john' @@ -337,7 +337,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do before do allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang end it "creates a user with dual LDAP and omniauth identities" do @@ -376,7 +376,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).with(uid, any_args).and_return(nil) allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).with(info_hash[:email], any_args).and_return(ldap_user) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang end it 'creates the LDAP identity' do @@ -392,7 +392,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do it "adds the omniauth identity to the LDAP account" do allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.username).to eql 'john' @@ -414,7 +414,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(nil) allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).and_return(ldap_user) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } expect(identities_as_hash).to match_array(result_identities(dn, uid)) @@ -426,7 +426,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).and_return(nil) allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_dn).and_return(ldap_user) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } expect(identities_as_hash).to match_array(result_identities(dn, uid)) @@ -447,7 +447,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it 'does not save the identity' do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } expect(identities_as_hash).to match_array([{ provider: 'twitter', extern_uid: uid }]) @@ -467,7 +467,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do it 'creates a user favoring the LDAP username and strips email domain' do allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.username).to eql 'johndoe' @@ -510,7 +510,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do before do allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang end it "creates a user with dual LDAP and omniauth identities" do @@ -549,7 +549,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do it "adds the omniauth identity to the LDAP account" do allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user) - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.username).to eql 'john' @@ -584,7 +584,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -596,7 +596,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).to be_blocked end @@ -622,7 +622,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -636,7 +636,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).to be_blocked end @@ -654,7 +654,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -668,7 +668,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -678,7 +678,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do context 'sign-in' do before do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang oauth_user.gl_user.activate end @@ -688,7 +688,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -700,7 +700,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -714,7 +714,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -728,7 +728,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end it do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -791,7 +791,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do context 'when collision with existing user' do it 'generates the username with a counter' do - oauth_user.save + oauth_user.save # rubocop:disable Rails/SaveBang oauth_user2 = described_class.new(OmniAuth::AuthHash.new(uid: 'my-uid2', provider: provider, info: { nickname: 'johngitlab-ETC@othermail.com', email: 'john@othermail.com' })) expect(oauth_user2.gl_user.username).to eq('johngitlab-ETC1') diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb index 6f3d6187076..5e9d07a8bf7 100644 --- a/spec/lib/gitlab/auth/request_authenticator_spec.rb +++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb @@ -21,8 +21,10 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do let_it_be(:session_user) { build(:user) } it 'returns sessionless user first' do - allow_any_instance_of(described_class).to receive(:find_sessionless_user).and_return(sessionless_user) - allow_any_instance_of(described_class).to receive(:find_user_from_warden).and_return(session_user) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:find_sessionless_user).and_return(sessionless_user) + allow(instance).to receive(:find_user_from_warden).and_return(session_user) + end expect(subject.user([:api])).to eq sessionless_user end diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb index fd48492f18d..796512bc52b 100644 --- a/spec/lib/gitlab/auth/saml/user_spec.rb +++ b/spec/lib/gitlab/auth/saml/user_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'and should bind with SAML' do it 'adds the SAML identity to the existing user' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).to eq existing_user identity = gl_user.identities.first @@ -49,7 +49,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'are defined' do it 'marks the user as external' do stub_saml_group_config(%w(Freelancers)) - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_truthy end @@ -61,7 +61,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'are defined but the user does not belong there' do it 'does not mark the user as external' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_falsey end @@ -70,7 +70,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'user was external, now should not be' do it 'makes user internal' do existing_user.update_attribute('external', true) - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_falsey end @@ -86,7 +86,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'creates a user from SAML' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid identity = gl_user.identities.first @@ -101,7 +101,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'does not throw an error' do - expect { saml_user.save }.not_to raise_error + expect { saml_user.save }.not_to raise_error # rubocop:disable Rails/SaveBang end end @@ -111,7 +111,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'throws an error' do - expect { saml_user.save }.to raise_error StandardError + expect { saml_user.save }.to raise_error StandardError # rubocop:disable Rails/SaveBang end end end @@ -120,7 +120,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'are defined' do it 'marks the user as external' do stub_saml_group_config(%w(Freelancers)) - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_truthy end @@ -129,7 +129,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'are defined but the user does not belong there' do it 'does not mark the user as external' do stub_saml_group_config(%w(Interns)) - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_falsey end @@ -170,7 +170,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'and no account for the LDAP user' do it 'creates a user with dual LDAP and SAML identities' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.username).to eql uid @@ -230,7 +230,7 @@ RSpec.describe Gitlab::Auth::Saml::User do { provider: id.provider, extern_uid: id.extern_uid } end - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.username).to eql 'john' @@ -259,7 +259,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'adds the omniauth identity to the LDAP account' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.username).to eql 'john' @@ -271,9 +271,9 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'saves successfully on subsequent tries, when both identities are present' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang local_saml_user = described_class.new(auth_hash) - local_saml_user.save + local_saml_user.save # rubocop:disable Rails/SaveBang expect(local_saml_user.gl_user).to be_valid expect(local_saml_user.gl_user).to be_persisted @@ -289,7 +289,7 @@ RSpec.describe Gitlab::Auth::Saml::User do local_hash = OmniAuth::AuthHash.new(uid: dn, provider: provider, info: info_hash) local_saml_user = described_class.new(local_hash) - local_saml_user.save + local_saml_user.save # rubocop:disable Rails/SaveBang local_gl_user = local_saml_user.gl_user expect(local_gl_user).to be_valid @@ -309,7 +309,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'creates the user' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted end @@ -321,7 +321,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'creates and confirms the user anyway' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted expect(gl_user).to be_confirmed @@ -334,7 +334,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'creates the user' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_persisted end @@ -353,7 +353,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'does not block the user' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -365,7 +365,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it 'blocks user' do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).to be_blocked end @@ -374,7 +374,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'sign-in' do before do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang saml_user.gl_user.activate end @@ -384,7 +384,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end @@ -396,7 +396,7 @@ RSpec.describe Gitlab::Auth::Saml::User do end it do - saml_user.save + saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user).not_to be_blocked end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 611c70d73a1..706344831b8 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -10,29 +10,29 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do describe 'constants' do it 'API_SCOPES contains all scopes for API access' do - expect(subject::API_SCOPES).to eq %i[api read_user read_api] + expect(subject::API_SCOPES).to match_array %i[api read_user read_api] end it 'ADMIN_SCOPES contains all scopes for ADMIN access' do - expect(subject::ADMIN_SCOPES).to eq %i[sudo] + expect(subject::ADMIN_SCOPES).to match_array %i[sudo] end it 'REPOSITORY_SCOPES contains all scopes for REPOSITORY access' do - expect(subject::REPOSITORY_SCOPES).to eq %i[read_repository write_repository] + expect(subject::REPOSITORY_SCOPES).to match_array %i[read_repository write_repository] end it 'OPENID_SCOPES contains all scopes for OpenID Connect' do - expect(subject::OPENID_SCOPES).to eq [:openid] + expect(subject::OPENID_SCOPES).to match_array [:openid] end it 'DEFAULT_SCOPES contains all default scopes' do - expect(subject::DEFAULT_SCOPES).to eq [:api] + expect(subject::DEFAULT_SCOPES).to match_array [:api] end it 'optional_scopes contains all non-default scopes' do stub_container_registry_config(enabled: true) - expect(subject.optional_scopes).to eq %i[read_user read_api read_repository write_repository read_registry write_registry sudo openid profile email] + expect(subject.optional_scopes).to match_array %i[read_user read_api read_repository write_repository read_registry write_registry sudo openid profile email] end end @@ -40,21 +40,21 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it 'contains all non-default scopes' do stub_container_registry_config(enabled: true) - expect(subject.all_available_scopes).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry sudo] + expect(subject.all_available_scopes).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry sudo] end it 'contains for non-admin user all non-default scopes without ADMIN access' do stub_container_registry_config(enabled: true) user = create(:user, admin: false) - expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry] + expect(subject.available_scopes_for(user)).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry] end it 'contains for admin user all non-default scopes with ADMIN access' do stub_container_registry_config(enabled: true) user = create(:user, admin: true) - expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry sudo] + expect(subject.available_scopes_for(user)).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry sudo] end context 'registry_scopes' do @@ -156,21 +156,36 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do let(:username) { 'gitlab-ci-token' } context 'for running build' do - let!(:build) { create(:ci_build, :running) } - let(:project) { build.project } + let!(:group) { create(:group) } + let!(:project) { create(:project, group: group) } + let!(:build) { create(:ci_build, :running, project: project) } it 'recognises user-less build' do expect(subject).to have_attributes(actor: nil, project: build.project, type: :ci, authentication_abilities: described_class.build_authentication_abilities) end it 'recognises user token' do - build.update(user: create(:user)) + build.update!(user: create(:user)) + + expect(subject).to have_attributes(actor: build.user, project: build.project, type: :build, authentication_abilities: described_class.build_authentication_abilities) + end + + it 'recognises project level bot access token' do + build.update!(user: create(:user, :project_bot)) + project.add_maintainer(build.user) + + expect(subject).to have_attributes(actor: build.user, project: build.project, type: :build, authentication_abilities: described_class.build_authentication_abilities) + end + + it 'recognises group level bot access token' do + build.update!(user: create(:user, :project_bot)) + group.add_maintainer(build.user) expect(subject).to have_attributes(actor: build.user, project: build.project, type: :build, authentication_abilities: described_class.build_authentication_abilities) end it 'fails with blocked user token' do - build.update(user: create(:user, :blocked)) + build.update!(user: create(:user, :blocked)) expect(subject).to have_attributes(auth_failure) end @@ -198,7 +213,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it 'recognizes other ci services' do project.create_drone_ci_integration(active: true) - project.drone_ci_integration.update(token: 'token') + project.drone_ci_integration.update!(token: 'token', drone_url: generate(:url)) expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to have_attributes(actor: nil, project: project, type: :ci, authentication_abilities: described_class.build_authentication_abilities) end @@ -311,7 +326,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do context 'orphaned token' do before do - user.destroy + user.destroy! end it_behaves_like 'an oauth failure' @@ -888,7 +903,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it 'resets failed_attempts when true and password is correct' do user.failed_attempts = 2 - user.save + user.save! expect do gl_auth.find_with_user_password(username, password, increment_failed_attempts: true) @@ -917,7 +932,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it 'does not reset failed_attempts when true and password is correct' do user.failed_attempts = 2 - user.save + user.save! expect do gl_auth.find_with_user_password(username, password, increment_failed_attempts: true) diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb index 1053ae2e325..073cee96ede 100644 --- a/spec/lib/gitlab/authorized_keys_spec.rb +++ b/spec/lib/gitlab/authorized_keys_spec.rb @@ -38,7 +38,7 @@ RSpec.describe Gitlab::AuthorizedKeys do end describe '#create' do - subject { authorized_keys.create } + subject { authorized_keys.create } # rubocop:disable Rails/SaveBang context 'authorized_keys file exists' do before do diff --git a/spec/lib/gitlab/background_migration/backfill_ci_queuing_tables_spec.rb b/spec/lib/gitlab/background_migration/backfill_ci_queuing_tables_spec.rb new file mode 100644 index 00000000000..1aac5970a77 --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_ci_queuing_tables_spec.rb @@ -0,0 +1,244 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BackfillCiQueuingTables, :migration, schema: 20220208115439 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:ci_cd_settings) { table(:project_ci_cd_settings) } + let(:builds) { table(:ci_builds) } + let(:queuing_entries) { table(:ci_pending_builds) } + let(:tags) { table(:tags) } + let(:taggings) { table(:taggings) } + + subject { described_class.new } + + describe '#perform' do + let!(:namespace) do + namespaces.create!( + id: 10, + name: 'namespace10', + path: 'namespace10', + traversal_ids: [10]) + end + + let!(:other_namespace) do + namespaces.create!( + id: 11, + name: 'namespace11', + path: 'namespace11', + traversal_ids: [11]) + end + + let!(:project) do + projects.create!(id: 5, namespace_id: 10, name: 'test1', path: 'test1') + end + + let!(:ci_cd_setting) do + ci_cd_settings.create!(id: 5, project_id: 5, group_runners_enabled: true) + end + + let!(:other_project) do + projects.create!(id: 7, namespace_id: 11, name: 'test2', path: 'test2') + end + + let!(:other_ci_cd_setting) do + ci_cd_settings.create!(id: 7, project_id: 7, group_runners_enabled: false) + end + + let!(:another_project) do + projects.create!(id: 9, namespace_id: 10, name: 'test3', path: 'test3', shared_runners_enabled: false) + end + + let!(:ruby_tag) do + tags.create!(id: 22, name: 'ruby') + end + + let!(:postgres_tag) do + tags.create!(id: 23, name: 'postgres') + end + + it 'creates ci_pending_builds for all pending builds in range' do + builds.create!(id: 50, status: :pending, name: 'test1', project_id: 5, type: 'Ci::Build') + builds.create!(id: 51, status: :created, name: 'test2', project_id: 5, type: 'Ci::Build') + builds.create!(id: 52, status: :pending, name: 'test3', project_id: 5, protected: true, type: 'Ci::Build') + + taggings.create!(taggable_id: 52, taggable_type: 'CommitStatus', tag_id: 22) + taggings.create!(taggable_id: 52, taggable_type: 'CommitStatus', tag_id: 23) + + builds.create!(id: 60, status: :pending, name: 'test1', project_id: 7, type: 'Ci::Build') + builds.create!(id: 61, status: :running, name: 'test2', project_id: 7, protected: true, type: 'Ci::Build') + builds.create!(id: 62, status: :pending, name: 'test3', project_id: 7, type: 'Ci::Build') + + taggings.create!(taggable_id: 60, taggable_type: 'CommitStatus', tag_id: 23) + taggings.create!(taggable_id: 62, taggable_type: 'CommitStatus', tag_id: 22) + + builds.create!(id: 70, status: :pending, name: 'test1', project_id: 9, protected: true, type: 'Ci::Build') + builds.create!(id: 71, status: :failed, name: 'test2', project_id: 9, type: 'Ci::Build') + builds.create!(id: 72, status: :pending, name: 'test3', project_id: 9, type: 'Ci::Build') + + taggings.create!(taggable_id: 71, taggable_type: 'CommitStatus', tag_id: 22) + + subject.perform(1, 100) + + expect(queuing_entries.all).to contain_exactly( + an_object_having_attributes( + build_id: 50, + project_id: 5, + namespace_id: 10, + protected: false, + instance_runners_enabled: true, + minutes_exceeded: false, + tag_ids: [], + namespace_traversal_ids: [10]), + an_object_having_attributes( + build_id: 52, + project_id: 5, + namespace_id: 10, + protected: true, + instance_runners_enabled: true, + minutes_exceeded: false, + tag_ids: match_array([22, 23]), + namespace_traversal_ids: [10]), + an_object_having_attributes( + build_id: 60, + project_id: 7, + namespace_id: 11, + protected: false, + instance_runners_enabled: true, + minutes_exceeded: false, + tag_ids: [23], + namespace_traversal_ids: []), + an_object_having_attributes( + build_id: 62, + project_id: 7, + namespace_id: 11, + protected: false, + instance_runners_enabled: true, + minutes_exceeded: false, + tag_ids: [22], + namespace_traversal_ids: []), + an_object_having_attributes( + build_id: 70, + project_id: 9, + namespace_id: 10, + protected: true, + instance_runners_enabled: false, + minutes_exceeded: false, + tag_ids: [], + namespace_traversal_ids: []), + an_object_having_attributes( + build_id: 72, + project_id: 9, + namespace_id: 10, + protected: false, + instance_runners_enabled: false, + minutes_exceeded: false, + tag_ids: [], + namespace_traversal_ids: []) + ) + end + + it 'skips builds that already have ci_pending_builds' do + builds.create!(id: 50, status: :pending, name: 'test1', project_id: 5, type: 'Ci::Build') + builds.create!(id: 51, status: :created, name: 'test2', project_id: 5, type: 'Ci::Build') + builds.create!(id: 52, status: :pending, name: 'test3', project_id: 5, protected: true, type: 'Ci::Build') + + taggings.create!(taggable_id: 50, taggable_type: 'CommitStatus', tag_id: 22) + taggings.create!(taggable_id: 52, taggable_type: 'CommitStatus', tag_id: 23) + + queuing_entries.create!(build_id: 50, project_id: 5, namespace_id: 10) + + subject.perform(1, 100) + + expect(queuing_entries.all).to contain_exactly( + an_object_having_attributes( + build_id: 50, + project_id: 5, + namespace_id: 10, + protected: false, + instance_runners_enabled: false, + minutes_exceeded: false, + tag_ids: [], + namespace_traversal_ids: []), + an_object_having_attributes( + build_id: 52, + project_id: 5, + namespace_id: 10, + protected: true, + instance_runners_enabled: true, + minutes_exceeded: false, + tag_ids: [23], + namespace_traversal_ids: [10]) + ) + end + + it 'upserts values in case of conflicts' do + builds.create!(id: 50, status: :pending, name: 'test1', project_id: 5, type: 'Ci::Build') + queuing_entries.create!(build_id: 50, project_id: 5, namespace_id: 10) + + build = described_class::Ci::Build.find(50) + described_class::Ci::PendingBuild.upsert_from_build!(build) + + expect(queuing_entries.all).to contain_exactly( + an_object_having_attributes( + build_id: 50, + project_id: 5, + namespace_id: 10, + protected: false, + instance_runners_enabled: true, + minutes_exceeded: false, + tag_ids: [], + namespace_traversal_ids: [10]) + ) + end + end + + context 'Ci::Build' do + describe '.each_batch' do + let(:model) { described_class::Ci::Build } + + before do + builds.create!(id: 1, status: :pending, name: 'test1', project_id: 5, type: 'Ci::Build') + builds.create!(id: 2, status: :pending, name: 'test2', project_id: 5, type: 'Ci::Build') + builds.create!(id: 3, status: :pending, name: 'test3', project_id: 5, type: 'Ci::Build') + builds.create!(id: 4, status: :pending, name: 'test4', project_id: 5, type: 'Ci::Build') + builds.create!(id: 5, status: :pending, name: 'test5', project_id: 5, type: 'Ci::Build') + end + + it 'yields an ActiveRecord::Relation when a block is given' do + model.each_batch do |relation| + expect(relation).to be_a_kind_of(ActiveRecord::Relation) + end + end + + it 'yields a batch index as the second argument' do + model.each_batch do |_, index| + expect(index).to eq(1) + end + end + + it 'accepts a custom batch size' do + amount = 0 + + model.each_batch(of: 1) { amount += 1 } + + expect(amount).to eq(5) + end + + it 'does not include ORDER BYs in the yielded relations' do + model.each_batch do |relation| + expect(relation.to_sql).not_to include('ORDER BY') + end + end + + it 'orders ascending' do + ids = [] + + model.each_batch(of: 1) { |rel| ids.concat(rel.ids) } + + expect(ids).to eq(ids.sort) + end + end + end +end diff --git a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb deleted file mode 100644 index c4013d002b2..00000000000 --- a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories do - it_behaves_like 'backfill migration for project repositories', :legacy -end diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route_spec.rb new file mode 100644 index 00000000000..b821efcadb0 --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceIdForNamespaceRoute, :migration, schema: 20220120123800 do + let(:migration) { described_class.new } + let(:namespaces_table) { table(:namespaces) } + let(:projects_table) { table(:projects) } + let(:routes_table) { table(:routes) } + + let(:table_name) { 'routes' } + let(:batch_column) { :id } + let(:sub_batch_size) { 200 } + let(:pause_ms) { 0 } + + let(:namespace1) { namespaces_table.create!(name: 'namespace1', path: 'namespace1', type: 'User') } + let(:namespace2) { namespaces_table.create!(name: 'namespace2', path: 'namespace2', type: 'Group') } + let(:namespace3) { namespaces_table.create!(name: 'namespace3', path: 'namespace3', type: 'Group') } + let(:namespace4) { namespaces_table.create!(name: 'namespace4', path: 'namespace4', type: 'Group') } + let(:project1) { projects_table.create!(name: 'project1', namespace_id: namespace1.id) } + + subject(:perform_migration) { migration.perform(1, 10, table_name, batch_column, sub_batch_size, pause_ms) } + + before do + routes_table.create!(id: 1, name: 'test1', path: 'test1', source_id: namespace1.id, + source_type: namespace1.class.sti_name) + routes_table.create!(id: 2, name: 'test2', path: 'test2', source_id: namespace2.id, + source_type: namespace2.class.sti_name) + routes_table.create!(id: 5, name: 'test3', path: 'test3', source_id: project1.id, + source_type: project1.class.sti_name) # should be ignored - project route + routes_table.create!(id: 6, name: 'test4', path: 'test4', source_id: non_existing_record_id, + source_type: namespace3.class.sti_name) # should be ignored - invalid source_id + routes_table.create!(id: 10, name: 'test5', path: 'test5', source_id: namespace3.id, + source_type: namespace3.class.sti_name) + routes_table.create!(id: 11, name: 'test6', path: 'test6', source_id: namespace4.id, + source_type: namespace4.class.sti_name) # should be ignored - outside the scope + end + + it 'backfills `type` for the selected records', :aggregate_failures do + perform_migration + + expect(routes_table.where.not(namespace_id: nil).pluck(:id)).to match_array([1, 2, 10]) + end + + it 'tracks timings of queries' do + expect(migration.batch_metrics.timings).to be_empty + + expect { perform_migration }.to change { migration.batch_metrics.timings } + end +end diff --git a/spec/lib/gitlab/background_migration/backfill_project_updated_at_after_repository_storage_move_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_updated_at_after_repository_storage_move_spec.rb deleted file mode 100644 index ed44b819a97..00000000000 --- a/spec/lib/gitlab/background_migration/backfill_project_updated_at_after_repository_storage_move_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::BackfillProjectUpdatedAtAfterRepositoryStorageMove, :migration, schema: 20210301200959 do - let(:projects) { table(:projects) } - let(:project_repository_storage_moves) { table(:project_repository_storage_moves) } - let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } - - subject { described_class.new } - - describe '#perform' do - it 'updates project updated_at column if they were moved to a different repository storage' do - freeze_time do - project_1 = projects.create!(id: 1, namespace_id: namespace.id, updated_at: 1.day.ago) - project_2 = projects.create!(id: 2, namespace_id: namespace.id, updated_at: Time.current) - original_project_3_updated_at = 2.minutes.from_now - project_3 = projects.create!(id: 3, namespace_id: namespace.id, updated_at: original_project_3_updated_at) - original_project_4_updated_at = 10.days.ago - project_4 = projects.create!(id: 4, namespace_id: namespace.id, updated_at: original_project_4_updated_at) - - repository_storage_move_1 = project_repository_storage_moves.create!(project_id: project_1.id, updated_at: 2.hours.ago, source_storage_name: 'default', destination_storage_name: 'default') - repository_storage_move_2 = project_repository_storage_moves.create!(project_id: project_2.id, updated_at: Time.current, source_storage_name: 'default', destination_storage_name: 'default') - project_repository_storage_moves.create!(project_id: project_3.id, updated_at: Time.current, source_storage_name: 'default', destination_storage_name: 'default') - - subject.perform([1, 2, 3, 4, non_existing_record_id]) - - expect(project_1.reload.updated_at).to eq(repository_storage_move_1.updated_at + 1.second) - expect(project_2.reload.updated_at).to eq(repository_storage_move_2.updated_at + 1.second) - expect(project_3.reload.updated_at).to eq(original_project_3_updated_at) - expect(project_4.reload.updated_at).to eq(original_project_4_updated_at) - end - end - end -end diff --git a/spec/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy_spec.rb new file mode 100644 index 00000000000..7b8a466b37c --- /dev/null +++ b/spec/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::BackfillProjectNamespacePerGroupBatchingStrategy, '#next_batch' do + let!(:namespaces) { table(:namespaces) } + let!(:projects) { table(:projects) } + let!(:background_migrations) { table(:batched_background_migrations) } + + let!(:namespace1) { namespaces.create!(name: 'batchtest1', type: 'Group', path: 'batch-test1') } + let!(:namespace2) { namespaces.create!(name: 'batchtest2', type: 'Group', parent_id: namespace1.id, path: 'batch-test2') } + let!(:namespace3) { namespaces.create!(name: 'batchtest3', type: 'Group', parent_id: namespace2.id, path: 'batch-test3') } + + let!(:project1) { projects.create!(name: 'project1', path: 'project1', namespace_id: namespace1.id, visibility_level: 20) } + let!(:project2) { projects.create!(name: 'project2', path: 'project2', namespace_id: namespace2.id, visibility_level: 20) } + let!(:project3) { projects.create!(name: 'project3', path: 'project3', namespace_id: namespace3.id, visibility_level: 20) } + let!(:project4) { projects.create!(name: 'project4', path: 'project4', namespace_id: namespace3.id, visibility_level: 20) } + let!(:batching_strategy) { described_class.new } + + let(:job_arguments) { [namespace1.id, 'up'] } + + context 'when starting on the first batch' do + it 'returns the bounds of the next batch' do + batch_bounds = batching_strategy.next_batch(:projects, :id, batch_min_value: project1.id, batch_size: 3, job_arguments: job_arguments) + + expect(batch_bounds).to match_array([project1.id, project3.id]) + end + end + + context 'when additional batches remain' do + it 'returns the bounds of the next batch' do + batch_bounds = batching_strategy.next_batch(:projects, :id, batch_min_value: project2.id, batch_size: 3, job_arguments: job_arguments) + + expect(batch_bounds).to match_array([project2.id, project4.id]) + end + end + + context 'when on the final batch' do + it 'returns the bounds of the next batch' do + batch_bounds = batching_strategy.next_batch(:projects, :id, batch_min_value: project4.id, batch_size: 3, job_arguments: job_arguments) + + expect(batch_bounds).to match_array([project4.id, project4.id]) + end + end + + context 'when no additional batches remain' do + it 'returns nil' do + batch_bounds = batching_strategy.next_batch(:projects, :id, batch_min_value: project4.id + 1, batch_size: 1, job_arguments: job_arguments) + + expect(batch_bounds).to be_nil + end + end +end diff --git a/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb index 8febe850e04..39030039125 100644 --- a/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb +++ b/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi context 'when starting on the first batch' do it 'returns the bounds of the next batch' do - batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace1.id, batch_size: 3) + batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace1.id, batch_size: 3, job_arguments: nil) expect(batch_bounds).to eq([namespace1.id, namespace3.id]) end @@ -21,7 +21,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi context 'when additional batches remain' do it 'returns the bounds of the next batch' do - batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace2.id, batch_size: 3) + batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace2.id, batch_size: 3, job_arguments: nil) expect(batch_bounds).to eq([namespace2.id, namespace4.id]) end @@ -29,7 +29,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi context 'when on the final batch' do it 'returns the bounds of the next batch' do - batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id, batch_size: 3) + batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id, batch_size: 3, job_arguments: nil) expect(batch_bounds).to eq([namespace4.id, namespace4.id]) end @@ -37,7 +37,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi context 'when no additional batches remain' do it 'returns nil' do - batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id + 1, batch_size: 1) + batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id + 1, batch_size: 1, job_arguments: nil) expect(batch_bounds).to be_nil end diff --git a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb index d4fc24d0559..90d9bbb42c3 100644 --- a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb +++ b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb @@ -7,13 +7,14 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo let(:test_table) { table(table_name) } let(:sub_batch_size) { 1000 } let(:pause_ms) { 0 } + let(:connection) { ApplicationRecord.connection } let(:helpers) do ActiveRecord::Migration.new.extend(Gitlab::Database::MigrationHelpers) end before do - ActiveRecord::Base.connection.execute(<<~SQL) + connection.execute(<<~SQL) CREATE TABLE #{table_name} ( id integer NOT NULL, @@ -34,12 +35,14 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo after do # Make sure that the temp table we created is dropped (it is not removed by the database_cleaner) - ActiveRecord::Base.connection.execute(<<~SQL) + connection.execute(<<~SQL) DROP TABLE IF EXISTS #{table_name}; SQL end - subject(:copy_columns) { described_class.new } + subject(:copy_columns) { described_class.new(connection: connection) } + + it { expect(described_class).to be < Gitlab::BackgroundMigration::BaseJob } describe '#perform' do let(:migration_class) { described_class.name } diff --git a/spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb b/spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb deleted file mode 100644 index 68fe8f39f59..00000000000 --- a/spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb +++ /dev/null @@ -1,134 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulateFindingUuidForVulnerabilityFeedback, schema: 20210301200959 do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:users) { table(:users) } - let(:scanners) { table(:vulnerability_scanners) } - let(:identifiers) { table(:vulnerability_identifiers) } - let(:findings) { table(:vulnerability_occurrences) } - let(:vulnerability_feedback) { table(:vulnerability_feedback) } - - let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') } - let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') } - let(:user) { users.create!(username: 'john.doe', projects_limit: 5) } - let(:scanner) { scanners.create!(project_id: project.id, external_id: 'foo', name: 'bar') } - let(:identifier) { identifiers.create!(project_id: project.id, fingerprint: 'foo', external_type: 'bar', external_id: 'zoo', name: 'baz') } - let(:sast_report) { 0 } - let(:dependency_scanning_report) { 1 } - let(:dast_report) { 3 } - let(:secret_detection_report) { 4 } - let(:project_fingerprint) { Digest::SHA1.hexdigest(SecureRandom.uuid) } - let(:location_fingerprint_1) { Digest::SHA1.hexdigest(SecureRandom.uuid) } - let(:location_fingerprint_2) { Digest::SHA1.hexdigest(SecureRandom.uuid) } - let(:location_fingerprint_3) { Digest::SHA1.hexdigest(SecureRandom.uuid) } - let(:finding_1) { finding_creator.call(sast_report, location_fingerprint_1) } - let(:finding_2) { finding_creator.call(dast_report, location_fingerprint_2) } - let(:finding_3) { finding_creator.call(secret_detection_report, location_fingerprint_3) } - let(:expected_uuid_1) do - Security::VulnerabilityUUID.generate( - report_type: 'sast', - primary_identifier_fingerprint: identifier.fingerprint, - location_fingerprint: location_fingerprint_1, - project_id: project.id - ) - end - - let(:expected_uuid_2) do - Security::VulnerabilityUUID.generate( - report_type: 'dast', - primary_identifier_fingerprint: identifier.fingerprint, - location_fingerprint: location_fingerprint_2, - project_id: project.id - ) - end - - let(:expected_uuid_3) do - Security::VulnerabilityUUID.generate( - report_type: 'secret_detection', - primary_identifier_fingerprint: identifier.fingerprint, - location_fingerprint: location_fingerprint_3, - project_id: project.id - ) - end - - let(:finding_creator) do - -> (report_type, location_fingerprint) do - findings.create!( - project_id: project.id, - primary_identifier_id: identifier.id, - scanner_id: scanner.id, - report_type: report_type, - uuid: SecureRandom.uuid, - name: 'Foo', - location_fingerprint: Gitlab::Database::ShaAttribute.serialize(location_fingerprint), - project_fingerprint: Gitlab::Database::ShaAttribute.serialize(project_fingerprint), - metadata_version: '1', - severity: 0, - confidence: 5, - raw_metadata: '{}' - ) - end - end - - let(:feedback_creator) do - -> (category, project_fingerprint) do - vulnerability_feedback.create!( - project_id: project.id, - author_id: user.id, - feedback_type: 0, - category: category, - project_fingerprint: project_fingerprint - ) - end - end - - let!(:feedback_1) { feedback_creator.call(finding_1.report_type, project_fingerprint) } - let!(:feedback_2) { feedback_creator.call(finding_2.report_type, project_fingerprint) } - let!(:feedback_3) { feedback_creator.call(finding_3.report_type, project_fingerprint) } - let!(:feedback_4) { feedback_creator.call(finding_1.report_type, 'foo') } - let!(:feedback_5) { feedback_creator.call(dependency_scanning_report, project_fingerprint) } - - subject(:populate_finding_uuids) { described_class.new.perform(feedback_1.id, feedback_5.id) } - - before do - allow(Gitlab::BackgroundMigration::Logger).to receive(:info) - end - - describe '#perform' do - it 'updates the `finding_uuid` attributes of the feedback records' do - expect { populate_finding_uuids }.to change { feedback_1.reload.finding_uuid }.from(nil).to(expected_uuid_1) - .and change { feedback_2.reload.finding_uuid }.from(nil).to(expected_uuid_2) - .and change { feedback_3.reload.finding_uuid }.from(nil).to(expected_uuid_3) - .and not_change { feedback_4.reload.finding_uuid } - .and not_change { feedback_5.reload.finding_uuid } - - expect(Gitlab::BackgroundMigration::Logger).to have_received(:info).once - end - - it 'preloads the finding and identifier records to prevent N+1 queries' do - # Load feedback records(1), load findings(2), load identifiers(3) and finally update feedback records one by one(6) - expect { populate_finding_uuids }.not_to exceed_query_limit(6) - end - - context 'when setting the `finding_uuid` attribute of a feedback record fails' do - let(:expected_error) { RuntimeError.new } - - before do - allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) - - allow_next_found_instance_of(described_class::VulnerabilityFeedback) do |feedback| - allow(feedback).to receive(:update_column).and_raise(expected_error) - end - end - - it 'captures the errors and does not crash entirely' do - expect { populate_finding_uuids }.not_to raise_error - - expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception).with(expected_error).exactly(3).times - end - end - end -end diff --git a/spec/lib/gitlab/background_migration/populate_issue_email_participants_spec.rb b/spec/lib/gitlab/background_migration/populate_issue_email_participants_spec.rb deleted file mode 100644 index b00eb185b34..00000000000 --- a/spec/lib/gitlab/background_migration/populate_issue_email_participants_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulateIssueEmailParticipants, schema: 20210301200959 do - let!(:namespace) { table(:namespaces).create!(name: 'namespace', path: 'namespace') } - let!(:project) { table(:projects).create!(id: 1, namespace_id: namespace.id) } - let!(:issue1) { table(:issues).create!(id: 1, project_id: project.id, service_desk_reply_to: "a@gitlab.com") } - let!(:issue2) { table(:issues).create!(id: 2, project_id: project.id, service_desk_reply_to: "b@gitlab.com") } - let(:issue_email_participants) { table(:issue_email_participants) } - - describe '#perform' do - it 'migrates email addresses from service desk issues', :aggregate_failures do - expect { subject.perform(1, 2) }.to change { issue_email_participants.count }.by(2) - - expect(issue_email_participants.find_by(issue_id: 1).email).to eq("a@gitlab.com") - expect(issue_email_participants.find_by(issue_id: 2).email).to eq("b@gitlab.com") - end - end -end diff --git a/spec/lib/gitlab/background_migration/populate_topics_non_private_projects_count_spec.rb b/spec/lib/gitlab/background_migration/populate_topics_non_private_projects_count_spec.rb new file mode 100644 index 00000000000..e72e3392210 --- /dev/null +++ b/spec/lib/gitlab/background_migration/populate_topics_non_private_projects_count_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::PopulateTopicsNonPrivateProjectsCount, schema: 20220125122640 do + it 'correctly populates the non private projects counters' do + namespaces = table(:namespaces) + projects = table(:projects) + topics = table(:topics) + project_topics = table(:project_topics) + + group = namespaces.create!(name: 'group', path: 'group') + project_public = projects.create!(namespace_id: group.id, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project_internal = projects.create!(namespace_id: group.id, visibility_level: Gitlab::VisibilityLevel::INTERNAL) + project_private = projects.create!(namespace_id: group.id, visibility_level: Gitlab::VisibilityLevel::PRIVATE) + topic_1 = topics.create!(name: 'Topic1') + topic_2 = topics.create!(name: 'Topic2') + topic_3 = topics.create!(name: 'Topic3') + topic_4 = topics.create!(name: 'Topic4') + topic_5 = topics.create!(name: 'Topic5') + topic_6 = topics.create!(name: 'Topic6') + topic_7 = topics.create!(name: 'Topic7') + topic_8 = topics.create!(name: 'Topic8') + + project_topics.create!(topic_id: topic_1.id, project_id: project_public.id) + project_topics.create!(topic_id: topic_2.id, project_id: project_internal.id) + project_topics.create!(topic_id: topic_3.id, project_id: project_private.id) + project_topics.create!(topic_id: topic_4.id, project_id: project_public.id) + project_topics.create!(topic_id: topic_4.id, project_id: project_internal.id) + project_topics.create!(topic_id: topic_5.id, project_id: project_public.id) + project_topics.create!(topic_id: topic_5.id, project_id: project_private.id) + project_topics.create!(topic_id: topic_6.id, project_id: project_internal.id) + project_topics.create!(topic_id: topic_6.id, project_id: project_private.id) + project_topics.create!(topic_id: topic_7.id, project_id: project_public.id) + project_topics.create!(topic_id: topic_7.id, project_id: project_internal.id) + project_topics.create!(topic_id: topic_7.id, project_id: project_private.id) + project_topics.create!(topic_id: topic_8.id, project_id: project_public.id) + + subject.perform(topic_1.id, topic_7.id) + + expect(topic_1.reload.non_private_projects_count).to eq(1) + expect(topic_2.reload.non_private_projects_count).to eq(1) + expect(topic_3.reload.non_private_projects_count).to eq(0) + expect(topic_4.reload.non_private_projects_count).to eq(2) + expect(topic_5.reload.non_private_projects_count).to eq(1) + expect(topic_6.reload.non_private_projects_count).to eq(1) + expect(topic_7.reload.non_private_projects_count).to eq(2) + expect(topic_8.reload.non_private_projects_count).to eq(0) + end +end diff --git a/spec/lib/gitlab/background_migration/populate_vulnerability_reads_spec.rb b/spec/lib/gitlab/background_migration/populate_vulnerability_reads_spec.rb new file mode 100644 index 00000000000..a265fa95b23 --- /dev/null +++ b/spec/lib/gitlab/background_migration/populate_vulnerability_reads_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::PopulateVulnerabilityReads do + let(:vulnerabilities) { table(:vulnerabilities) } + let(:vulnerability_reads) { table(:vulnerability_reads) } + let(:vulnerabilities_findings) { table(:vulnerability_occurrences) } + let(:vulnerability_issue_links) { table(:vulnerability_issue_links) } + let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } + let(:user) { table(:users).create!(email: 'author@example.com', username: 'author', projects_limit: 10) } + let(:project) { table(:projects).create!(namespace_id: namespace.id) } + let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') } + let(:sub_batch_size) { 1000 } + + before do + vulnerabilities_findings.connection.execute 'ALTER TABLE vulnerability_occurrences DISABLE TRIGGER "trigger_insert_or_update_vulnerability_reads_from_occurrences"' + vulnerabilities.connection.execute 'ALTER TABLE vulnerabilities DISABLE TRIGGER "trigger_update_vulnerability_reads_on_vulnerability_update"' + vulnerability_issue_links.connection.execute 'ALTER TABLE vulnerability_issue_links DISABLE TRIGGER "trigger_update_has_issues_on_vulnerability_issue_links_update"' + + 10.times.each do |x| + vulnerability = create_vulnerability!( + project_id: project.id, + report_type: 7, + author_id: user.id + ) + identifier = table(:vulnerability_identifiers).create!( + project_id: project.id, + external_type: 'uuid-v5', + external_id: 'uuid-v5', + fingerprint: Digest::SHA1.hexdigest("#{vulnerability.id}"), + name: 'Identifier for UUIDv5') + + create_finding!( + vulnerability_id: vulnerability.id, + project_id: project.id, + scanner_id: scanner.id, + primary_identifier_id: identifier.id + ) + end + end + + it 'creates vulnerability_reads for the given records' do + described_class.new.perform(vulnerabilities.first.id, vulnerabilities.last.id, sub_batch_size) + + expect(vulnerability_reads.count).to eq(10) + end + + it 'does not create new records when records already exists' do + described_class.new.perform(vulnerabilities.first.id, vulnerabilities.last.id, sub_batch_size) + described_class.new.perform(vulnerabilities.first.id, vulnerabilities.last.id, sub_batch_size) + + expect(vulnerability_reads.count).to eq(10) + end + + private + + def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0) + vulnerabilities.create!( + project_id: project_id, + author_id: author_id, + title: title, + severity: severity, + confidence: confidence, + report_type: report_type + ) + end + + # rubocop:disable Metrics/ParameterLists + def create_finding!( + vulnerability_id: nil, project_id:, scanner_id:, primary_identifier_id:, + name: "test", severity: 7, confidence: 7, report_type: 0, + project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test', + metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid) + vulnerabilities_findings.create!( + vulnerability_id: vulnerability_id, + project_id: project_id, + name: name, + severity: severity, + confidence: confidence, + report_type: report_type, + project_fingerprint: project_fingerprint, + scanner_id: scanner_id, + primary_identifier_id: primary_identifier_id, + location: location, + location_fingerprint: location_fingerprint, + metadata_version: metadata_version, + raw_metadata: raw_metadata, + uuid: uuid + ) + end + # rubocop:enable Metrics/ParameterLists +end diff --git a/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb b/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb index 24259b06469..2c5de448fbc 100644 --- a/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb +++ b/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa start_id = ::Project.minimum(:id) end_id = ::Project.maximum(:id) projects_count = ::Project.count - batches_count = (projects_count / described_class::BATCH_SIZE.to_f).ceil + batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil project_namespaces_count = ::Namespace.where(type: 'Project').count migration = described_class.new @@ -39,7 +39,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa expect(migration).to receive(:batch_update_projects).exactly(batches_count).and_call_original expect(migration).to receive(:batch_update_project_namespaces_traversal_ids).exactly(batches_count).and_call_original - expect { migration.perform(start_id, end_id, nil, 'up') }.to change(Namespace.where(type: 'Project'), :count) + expect { migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up') }.to change(Namespace.where(type: 'Project'), :count) expect(projects_count).to eq(::Namespace.where(type: 'Project').count) check_projects_in_sync_with(Namespace.where(type: 'Project')) @@ -53,7 +53,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa start_id = backfilled_namespace_projects.minimum(:id) end_id = backfilled_namespace_projects.maximum(:id) group_projects_count = backfilled_namespace_projects.count - batches_count = (group_projects_count / described_class::BATCH_SIZE.to_f).ceil + batches_count = (group_projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil project_namespaces_in_hierarchy = project_namespaces_in_hierarchy(base_ancestor(backfilled_namespace)) migration = described_class.new @@ -66,7 +66,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa expect(group_projects_count).to eq(14) expect(project_namespaces_in_hierarchy.count).to eq(0) - migration.perform(start_id, end_id, backfilled_namespace.id, 'up') + migration.perform(start_id, end_id, nil, nil, nil, nil, backfilled_namespace.id, 'up') expect(project_namespaces_in_hierarchy.count).to eq(14) check_projects_in_sync_with(project_namespaces_in_hierarchy) @@ -79,7 +79,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa start_id = hierarchy1_projects.minimum(:id) end_id = hierarchy1_projects.maximum(:id) - described_class.new.perform(start_id, end_id, parent_group1.id, 'up') + described_class.new.perform(start_id, end_id, nil, nil, nil, nil, parent_group1.id, 'up') end it 'does not duplicate project namespaces' do @@ -87,7 +87,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa projects_count = ::Project.count start_id = ::Project.minimum(:id) end_id = ::Project.maximum(:id) - batches_count = (projects_count / described_class::BATCH_SIZE.to_f).ceil + batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil project_namespaces = ::Namespace.where(type: 'Project') migration = described_class.new @@ -100,7 +100,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa expect(migration).to receive(:batch_update_projects).exactly(batches_count).and_call_original expect(migration).to receive(:batch_update_project_namespaces_traversal_ids).exactly(batches_count).and_call_original - expect { migration.perform(start_id, end_id, nil, 'up') }.to change(project_namespaces, :count).by(14) + expect { migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up') }.to change(project_namespaces, :count).by(14) expect(projects_count).to eq(project_namespaces.count) end @@ -125,7 +125,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa context 'back-fill project namespaces in batches' do before do - stub_const("#{described_class.name}::BATCH_SIZE", 2) + stub_const("#{described_class.name}::SUB_BATCH_SIZE", 2) end it_behaves_like 'back-fill project namespaces' @@ -137,7 +137,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa start_id = ::Project.minimum(:id) end_id = ::Project.maximum(:id) # back-fill first - described_class.new.perform(start_id, end_id, nil, 'up') + described_class.new.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up') end shared_examples 'cleanup project namespaces' do @@ -146,7 +146,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa start_id = ::Project.minimum(:id) end_id = ::Project.maximum(:id) migration = described_class.new - batches_count = (projects_count / described_class::BATCH_SIZE.to_f).ceil + batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil expect(projects_count).to be > 0 expect(projects_count).to eq(::Namespace.where(type: 'Project').count) @@ -154,7 +154,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa expect(migration).to receive(:nullify_project_namespaces_in_projects).exactly(batches_count).and_call_original expect(migration).to receive(:delete_project_namespace_records).exactly(batches_count).and_call_original - migration.perform(start_id, end_id, nil, 'down') + migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'down') expect(::Project.count).to be > 0 expect(::Namespace.where(type: 'Project').count).to eq(0) @@ -168,7 +168,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa start_id = backfilled_namespace_projects.minimum(:id) end_id = backfilled_namespace_projects.maximum(:id) group_projects_count = backfilled_namespace_projects.count - batches_count = (group_projects_count / described_class::BATCH_SIZE.to_f).ceil + batches_count = (group_projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil project_namespaces_in_hierarchy = project_namespaces_in_hierarchy(base_ancestor(backfilled_namespace)) migration = described_class.new @@ -176,7 +176,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa expect(migration).to receive(:nullify_project_namespaces_in_projects).exactly(batches_count).and_call_original expect(migration).to receive(:delete_project_namespace_records).exactly(batches_count).and_call_original - migration.perform(start_id, end_id, backfilled_namespace.id, 'down') + migration.perform(start_id, end_id, nil, nil, nil, nil, backfilled_namespace.id, 'down') expect(::Namespace.where(type: 'Project').count).to be > 0 expect(project_namespaces_in_hierarchy.count).to eq(0) @@ -190,7 +190,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa context 'cleanup project namespaces in batches' do before do - stub_const("#{described_class.name}::BATCH_SIZE", 2) + stub_const("#{described_class.name}::SUB_BATCH_SIZE", 2) end it_behaves_like 'cleanup project namespaces' diff --git a/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb b/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb index 7214225c32c..f6f4a3f6115 100644 --- a/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb +++ b/spec/lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings_spec.rb @@ -87,7 +87,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveDuplicateVulnerabilitiesFindin let!(:unrelated_finding) do create_finding!( id: 9999999, - uuid: "unreleated_finding", + uuid: Gitlab::UUID.v5(SecureRandom.hex), vulnerability_id: nil, report_type: 1, location_fingerprint: 'random_location_fingerprint', diff --git a/spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb b/spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb deleted file mode 100644 index 17fe25c7f71..00000000000 --- a/spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb +++ /dev/null @@ -1,130 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::WrongfullyConfirmedEmailUnconfirmer, schema: 20210301200959 do - let(:users) { table(:users) } - let(:emails) { table(:emails) } - let(:user_synced_attributes_metadata) { table(:user_synced_attributes_metadata) } - let(:confirmed_at_2_days_ago) { 2.days.ago } - let(:confirmed_at_3_days_ago) { 3.days.ago } - let(:one_year_ago) { 1.year.ago } - - let!(:user_needs_migration_1) { users.create!(name: 'user1', email: 'test1@test.com', state: 'active', projects_limit: 1, confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) } - let!(:user_needs_migration_2) { users.create!(name: 'user2', email: 'test2@test.com', unconfirmed_email: 'unconfirmed@test.com', state: 'active', projects_limit: 1, confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) } - let!(:user_does_not_need_migration) { users.create!(name: 'user3', email: 'test3@test.com', state: 'active', projects_limit: 1) } - let!(:inactive_user) { users.create!(name: 'user4', email: 'test4@test.com', state: 'blocked', projects_limit: 1, confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) } - let!(:alert_bot_user) { users.create!(name: 'user5', email: 'test5@test.com', state: 'active', user_type: 2, projects_limit: 1, confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) } - let!(:user_has_synced_email) { users.create!(name: 'user6', email: 'test6@test.com', state: 'active', projects_limit: 1, confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) } - let!(:synced_attributes_metadata_for_user) { user_synced_attributes_metadata.create!(user_id: user_has_synced_email.id, email_synced: true) } - - let!(:bad_email_1) { emails.create!(user_id: user_needs_migration_1.id, email: 'other1@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) } - let!(:bad_email_2) { emails.create!(user_id: user_needs_migration_2.id, email: 'other2@test.com', confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) } - let!(:bad_email_3_inactive_user) { emails.create!(user_id: inactive_user.id, email: 'other-inactive@test.com', confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) } - let!(:bad_email_4_bot_user) { emails.create!(user_id: alert_bot_user.id, email: 'other-bot@test.com', confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) } - - let!(:good_email_1) { emails.create!(user_id: user_needs_migration_2.id, email: 'other3@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) } - let!(:good_email_2) { emails.create!(user_id: user_needs_migration_2.id, email: 'other4@test.com', confirmed_at: nil) } - let!(:good_email_3) { emails.create!(user_id: user_does_not_need_migration.id, email: 'other5@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) } - - let!(:second_email_for_user_with_synced_email) { emails.create!(user_id: user_has_synced_email.id, email: 'other6@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) } - - subject do - email_ids = [bad_email_1, bad_email_2, good_email_1, good_email_2, good_email_3, second_email_for_user_with_synced_email].map(&:id) - - described_class.new.perform(email_ids.min, email_ids.max) - end - - it 'does not change irrelevant email records' do - subject - - expect(good_email_1.reload.confirmed_at).to be_within(1.second).of(confirmed_at_2_days_ago) - expect(good_email_2.reload.confirmed_at).to be_nil - expect(good_email_3.reload.confirmed_at).to be_within(1.second).of(confirmed_at_2_days_ago) - - expect(bad_email_3_inactive_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago) - expect(bad_email_4_bot_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago) - - expect(good_email_1.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago) - expect(good_email_2.reload.confirmation_sent_at).to be_nil - expect(good_email_3.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago) - - expect(bad_email_3_inactive_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago) - expect(bad_email_4_bot_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago) - end - - it 'clears the `unconfirmed_email` field' do - subject - - user_needs_migration_2.reload - expect(user_needs_migration_2.unconfirmed_email).to be_nil - end - - it 'does not change irrelevant user records' do - subject - - expect(user_does_not_need_migration.reload.confirmed_at).to be_nil - expect(inactive_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago) - expect(alert_bot_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago) - expect(user_has_synced_email.reload.confirmed_at).to be_within(1.second).of(confirmed_at_2_days_ago) - - expect(user_does_not_need_migration.reload.confirmation_sent_at).to be_nil - expect(inactive_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago) - expect(alert_bot_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago) - expect(user_has_synced_email.confirmation_sent_at).to be_within(1.second).of(one_year_ago) - end - - it 'updates confirmation_sent_at column' do - subject - - expect(user_needs_migration_1.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now) - expect(user_needs_migration_2.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now) - - expect(bad_email_1.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now) - expect(bad_email_2.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now) - end - - it 'unconfirms bad email records' do - subject - - expect(bad_email_1.reload.confirmed_at).to be_nil - expect(bad_email_2.reload.confirmed_at).to be_nil - - expect(bad_email_1.reload.confirmation_token).not_to be_nil - expect(bad_email_2.reload.confirmation_token).not_to be_nil - end - - it 'unconfirms user records' do - subject - - expect(user_needs_migration_1.reload.confirmed_at).to be_nil - expect(user_needs_migration_2.reload.confirmed_at).to be_nil - - expect(user_needs_migration_1.reload.confirmation_token).not_to be_nil - expect(user_needs_migration_2.reload.confirmation_token).not_to be_nil - end - - context 'enqueued jobs' do - let(:user_1) { User.find(user_needs_migration_1.id) } - let(:user_2) { User.find(user_needs_migration_2.id) } - - let(:email_1) { Email.find(bad_email_1.id) } - let(:email_2) { Email.find(bad_email_2.id) } - - it 'enqueues the email confirmation and the unconfirm notification mailer jobs' do - allow(DeviseMailer).to receive(:confirmation_instructions).and_call_original - allow(Gitlab::BackgroundMigration::Mailers::UnconfirmMailer).to receive(:unconfirm_notification_email).and_call_original - - subject - - expect(DeviseMailer).to have_received(:confirmation_instructions).with(email_1, email_1.confirmation_token) - expect(DeviseMailer).to have_received(:confirmation_instructions).with(email_2, email_2.confirmation_token) - - expect(Gitlab::BackgroundMigration::Mailers::UnconfirmMailer).to have_received(:unconfirm_notification_email).with(user_1) - expect(DeviseMailer).to have_received(:confirmation_instructions).with(user_1, user_1.confirmation_token) - - expect(Gitlab::BackgroundMigration::Mailers::UnconfirmMailer).to have_received(:unconfirm_notification_email).with(user_2) - expect(DeviseMailer).to have_received(:confirmation_instructions).with(user_2, user_2.confirmation_token) - end - end -end diff --git a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb index 0380ddd9a2e..d2abdb740f8 100644 --- a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb @@ -22,8 +22,8 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do data: { project_key: project_key, repo_slug: repo_slug }, credentials: { base_uri: import_url, user: bitbucket_user, password: password } ) - data.save - project.save + data.save! + project.save! end describe '#import_repository' do diff --git a/spec/lib/gitlab/buffered_io_spec.rb b/spec/lib/gitlab/buffered_io_spec.rb new file mode 100644 index 00000000000..f8896abd46e --- /dev/null +++ b/spec/lib/gitlab/buffered_io_spec.rb @@ -0,0 +1,54 @@ +# rubocop:disable Style/FrozenStringLiteralComment +require 'spec_helper' + +RSpec.describe Gitlab::BufferedIo do + describe '#readuntil' do + let(:never_ending_tcp_socket) do + Class.new do + def initialize(*_) + @read_counter = 0 + end + + def setsockopt(*_); end + + def closed? + false + end + + def close + true + end + + def to_io + StringIO.new('Hello World!') + end + + def write_nonblock(data, *_) + data.size + end + + def read_nonblock(buffer_size, *_) + sleep 0.01 + @read_counter += 1 + + raise 'Test did not raise HeaderReadTimeout' if @read_counter > 10 + + 'H' * buffer_size + end + end + end + + before do + stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1) + end + + subject(:readuntil) do + Gitlab::BufferedIo.new(never_ending_tcp_socket.new).readuntil('a') + end + + it 'raises a timeout error' do + expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/) + end + end +end +# rubocop:enable Style/FrozenStringLiteralComment diff --git a/spec/lib/gitlab/changelog/config_spec.rb b/spec/lib/gitlab/changelog/config_spec.rb index c410ba4d116..600682d30ad 100644 --- a/spec/lib/gitlab/changelog/config_spec.rb +++ b/spec/lib/gitlab/changelog/config_spec.rb @@ -31,6 +31,20 @@ RSpec.describe Gitlab::Changelog::Config do described_class.from_git(project) end + + context 'when changelog is empty' do + it 'returns the default configuration' do + allow(project.repository) + .to receive(:changelog_config) + .and_return("") + + expect(described_class) + .to receive(:new) + .with(project) + + described_class.from_git(project) + end + end end describe '.from_hash' do diff --git a/spec/lib/gitlab/changelog/release_spec.rb b/spec/lib/gitlab/changelog/release_spec.rb index d8434821640..defcec5aa65 100644 --- a/spec/lib/gitlab/changelog/release_spec.rb +++ b/spec/lib/gitlab/changelog/release_spec.rb @@ -139,6 +139,16 @@ RSpec.describe Gitlab::Changelog::Release do OUT end end + + context 'when template parser raises an error' do + before do + allow(config).to receive(:template).and_raise(Gitlab::TemplateParser::Error) + end + + it 'raises a Changelog error' do + expect { release.to_markdown }.to raise_error(Gitlab::Changelog::Error) + end + end end describe '#header_start_position' do diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb index f503759f3f8..c06d26d1441 100644 --- a/spec/lib/gitlab/checks/branch_check_spec.rb +++ b/spec/lib/gitlab/checks/branch_check_spec.rb @@ -40,15 +40,6 @@ RSpec.describe Gitlab::Checks::BranchCheck do expect { subject.validate! }.not_to raise_error end end - - context "the feature flag is disabled" do - it "doesn't prohibit a 40-character hexadecimal branch name" do - stub_feature_flags(prohibit_hexadecimal_branch_names: false) - allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e") - - expect { subject.validate! }.not_to raise_error - end - end end context 'protected branches check' do diff --git a/spec/lib/gitlab/ci/badge/release/latest_release_spec.rb b/spec/lib/gitlab/ci/badge/release/latest_release_spec.rb new file mode 100644 index 00000000000..36f9f4fb321 --- /dev/null +++ b/spec/lib/gitlab/ci/badge/release/latest_release_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Badge::Release::LatestRelease do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + project.add_guest(user) + create(:release, project: project, released_at: 1.day.ago) + end + + subject { described_class.new(project, user) } + + describe '#entity' do + it 'describes latest release' do + expect(subject.entity).to eq 'Latest Release' + end + end + + describe '#tag' do + it 'returns latest release tag for the project ordered using release_at' do + create(:release, tag: "v1.0.0", project: project, released_at: 1.hour.ago) + latest_release = create(:release, tag: "v2.0.0", project: project, released_at: Time.current) + + expect(subject.tag).to eq latest_release.tag + end + end + + describe '#metadata' do + it 'returns correct metadata' do + expect(subject.metadata.image_url).to include 'release.svg' + end + end + + describe '#template' do + it 'returns correct template' do + expect(subject.template.key_text).to eq 'Latest Release' + end + end +end diff --git a/spec/lib/gitlab/ci/badge/release/metadata_spec.rb b/spec/lib/gitlab/ci/badge/release/metadata_spec.rb new file mode 100644 index 00000000000..d68358f1458 --- /dev/null +++ b/spec/lib/gitlab/ci/badge/release/metadata_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'lib/gitlab/ci/badge/shared/metadata' + +RSpec.describe Gitlab::Ci::Badge::Release::Metadata do + let(:project) { create(:project) } + let(:ref) { 'feature' } + let!(:release) { create(:release, tag: ref, project: project) } + let(:user) { create(:user) } + let(:badge) do + Gitlab::Ci::Badge::Release::LatestRelease.new(project, user) + end + + let(:metadata) { described_class.new(badge) } + + before do + project.add_guest(user) + end + + it_behaves_like 'badge metadata' + + describe '#title' do + it 'returns latest release title' do + expect(metadata.title).to eq 'Latest Release' + end + end + + describe '#image_url' do + it 'returns valid url' do + expect(metadata.image_url).to include "/-/badges/release.svg" + end + end + + describe '#link_url' do + it 'returns valid link' do + expect(metadata.link_url).to include "/-/releases" + end + end +end diff --git a/spec/lib/gitlab/ci/badge/release/template_spec.rb b/spec/lib/gitlab/ci/badge/release/template_spec.rb new file mode 100644 index 00000000000..2b66c296a94 --- /dev/null +++ b/spec/lib/gitlab/ci/badge/release/template_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Badge::Release::Template do + let(:project) { create(:project) } + let(:ref) { 'v1.2.3' } + let(:user) { create(:user) } + let!(:release) { create(:release, tag: ref, project: project) } + let(:badge) { Gitlab::Ci::Badge::Release::LatestRelease.new(project, user) } + let(:template) { described_class.new(badge) } + + before do + project.add_guest(user) + end + + describe '#key_text' do + it 'defaults to latest release' do + expect(template.key_text).to eq 'Latest Release' + end + + it 'returns custom key text' do + key_text = 'Test Release' + badge = Gitlab::Ci::Badge::Release::LatestRelease.new(project, user, opts: { key_text: key_text }) + + expect(described_class.new(badge).key_text).to eq key_text + end + end + + describe '#value_text' do + context 'when a release exists' do + it 'returns the tag of the release' do + expect(template.value_text).to eq ref + end + end + + context 'no releases exist' do + before do + allow(badge).to receive(:tag).and_return(nil) + end + + it 'returns string that latest release is none' do + expect(template.value_text).to eq 'none' + end + end + end + + describe '#key_width' do + it 'returns the default key width' do + expect(template.key_width).to eq 90 + end + + it 'returns custom key width' do + key_width = 100 + badge = Gitlab::Ci::Badge::Release::LatestRelease.new(project, user, opts: { key_width: key_width }) + + expect(described_class.new(badge).key_width).to eq key_width + end + end + + describe '#value_width' do + it 'has a fixed value width' do + expect(template.value_width).to eq 54 + end + end + + describe '#key_color' do + it 'always has the same color' do + expect(template.key_color).to eq '#555' + end + end + + describe '#value_color' do + context 'when release exists' do + it 'is blue' do + expect(template.value_color).to eq '#3076af' + end + end + + context 'when release does not exist' do + before do + allow(badge).to receive(:tag).and_return(nil) + end + + it 'is red' do + expect(template.value_color).to eq '#e05d44' + end + end + end +end diff --git a/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb b/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb index 0e26a9fa571..889878cf3ef 100644 --- a/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Build::Artifacts::ExpireInParser do - describe '.validate_duration' do + describe '.validate_duration', :request_store do subject { described_class.validate_duration(value) } context 'with never' do @@ -20,14 +20,33 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::ExpireInParser do context 'with a duration' do let(:value) { '1 Day' } + let(:other_value) { '30 seconds' } it { is_expected.to be_truthy } + + it 'caches data' do + expect(ChronicDuration).to receive(:parse).with(value).once.and_call_original + expect(ChronicDuration).to receive(:parse).with(other_value).once.and_call_original + + 2.times do + expect(described_class.validate_duration(value)).to eq(86400) + expect(described_class.validate_duration(other_value)).to eq(30) + end + end end context 'without a duration' do let(:value) { 'something' } it { is_expected.to be_falsy } + + it 'caches data' do + expect(ChronicDuration).to receive(:parse).with(value).once.and_call_original + + 2.times do + expect(described_class.validate_duration(value)).to be_falsey + end + end end end diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb index 532c83f6768..4ac8bf61738 100644 --- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb @@ -4,14 +4,23 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do describe '#satisfied_by?' do + subject { described_class.new(globs).satisfied_by?(pipeline, context) } + it_behaves_like 'a glob matching rule' do let(:pipeline) { build(:ci_pipeline) } + let(:context) {} before do allow(pipeline).to receive(:modified_paths).and_return(files.keys) end + end - subject { described_class.new(globs).satisfied_by?(pipeline, nil) } + context 'when pipeline is nil' do + let(:pipeline) {} + let(:context) {} + let(:globs) { [] } + + it { is_expected.to be_truthy } end context 'when using variable expansion' do @@ -20,8 +29,6 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do let(:globs) { ['$HELM_DIR/**/*'] } let(:context) { double('context') } - subject { described_class.new(globs).satisfied_by?(pipeline, context) } - before do allow(pipeline).to receive(:modified_paths).and_return(modified_paths) end @@ -32,6 +39,12 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do it { is_expected.to be_falsey } end + context 'when modified paths are nil' do + let(:modified_paths) {} + + it { is_expected.to be_truthy } + end + context 'when context has the specified variables' do let(:variables_hash) do { 'HELM_DIR' => 'helm' } diff --git a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb index 0505b17ea91..e83d4974bb7 100644 --- a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fast_spec_helper' +require_dependency 'active_model' RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do let(:factory) do diff --git a/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb index c255d6e9dd6..d5988dbbb58 100644 --- a/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fast_spec_helper' +require_dependency 'active_model' RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules do let(:factory) do diff --git a/spec/lib/gitlab/ci/config/entry/include_spec.rb b/spec/lib/gitlab/ci/config/entry/include_spec.rb index 275cdcddeb0..fd7f85c9298 100644 --- a/spec/lib/gitlab/ci/config/entry/include_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/include_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fast_spec_helper' +require_dependency 'active_model' RSpec.describe ::Gitlab::Ci::Config::Entry::Include do subject(:include_entry) { described_class.new(config) } diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index 9a2a67389fc..b03175cd80f 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -70,6 +70,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do it 'reports error' do expect(entry.errors).to include 'jobs rspec config should implement a script: or a trigger: keyword' end + + context 'when the job name cannot be cast directly to a symbol' do + let(:config) { { true => nil } } + + it 'properly parses the job name without raising a NoMethodError' do + expect(entry.errors).to include 'jobs true config should implement a script: or a trigger: keyword' + end + end end context 'when no visible jobs present' do diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 46800055dd9..e5de0fb38e3 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'support/helpers/stub_feature_flags' -require_dependency 'active_model' +require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Policy do let(:entry) { described_class.new(config) } @@ -47,6 +45,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Policy do end context 'when using unsafe regexp' do + # When removed we could use `require 'fast_spec_helper'` again. include StubFeatureFlags let(:config) { ['/^(?!master).+/'] } @@ -89,7 +88,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Policy do describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include /policy config should be an array of strings or regexps/ + .to include /policy config should be an array of strings or regular expressions/ end end end @@ -107,6 +106,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Policy do end context 'when using unsafe regexp' do + # When removed we could use `require 'fast_spec_helper'` again. include StubFeatureFlags let(:config) { { refs: ['/^(?!master).+/'] } } diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 749d1386ed9..daf58aff116 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -55,13 +55,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do } end - context 'when deprecated types keyword is defined' do + context 'when deprecated types/type keywords are defined' do let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:hash) do { types: %w(test deploy), - rspec: { script: 'rspec' } } + rspec: { script: 'rspec', type: 'test' } } end before do @@ -69,11 +69,15 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do end it 'returns array of types as stages with a warning' do + expect(root.jobs_value[:rspec][:stage]).to eq 'test' expect(root.stages_value).to eq %w[test deploy] - expect(root.warnings).to match_array(["root `types` is deprecated in 9.0 and will be removed in 15.0."]) + expect(root.warnings).to match_array([ + "root `types` is deprecated in 9.0 and will be removed in 15.0.", + "jobs:rspec `type` is deprecated in 9.0 and will be removed in 15.0." + ]) end - it 'logs usage of types keyword' do + it 'logs usage of keywords' do expect(Gitlab::AppJsonLogger).to( receive(:info) .with(event: 'ci_used_deprecated_keyword', @@ -350,9 +354,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do root.compose! end - context 'when before script is not an array' do + context 'when before script is a number' do let(:hash) do - { before_script: 'ls' } + { before_script: 123 } end describe '#valid?' do @@ -364,7 +368,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do describe '#errors' do it 'reports errors from child nodes' do expect(root.errors) - .to include 'before_script config should be an array containing strings and arrays of strings' + .to include 'before_script config should be a string or a nested array of strings up to 10 levels deep' end end end diff --git a/spec/lib/gitlab/ci/config/entry/script_spec.rb b/spec/lib/gitlab/ci/config/entry/script_spec.rb deleted file mode 100644 index 1ddf7881e81..00000000000 --- a/spec/lib/gitlab/ci/config/entry/script_spec.rb +++ /dev/null @@ -1,109 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Ci::Config::Entry::Script do - let(:entry) { described_class.new(config) } - - describe 'validations' do - context 'when entry config value is array of strings' do - let(:config) { %w(ls pwd) } - - describe '#value' do - it 'returns array of strings' do - expect(entry.value).to eq config - end - end - - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end - end - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when entry config value is array of arrays of strings' do - let(:config) { [['ls'], ['pwd', 'echo 1']] } - - describe '#value' do - it 'returns array of strings' do - expect(entry.value).to eq ['ls', 'pwd', 'echo 1'] - end - end - - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end - end - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when entry config value is array containing strings and arrays of strings' do - let(:config) { ['ls', ['pwd', 'echo 1']] } - - describe '#value' do - it 'returns array of strings' do - expect(entry.value).to eq ['ls', 'pwd', 'echo 1'] - end - end - - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end - end - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when entry value is string' do - let(:config) { 'ls' } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'script config should be an array containing strings and arrays of strings' - end - end - - describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid - end - end - end - - context 'when entry value is multi-level nested array' do - let(:config) { [['ls', ['echo 1']], 'pwd'] } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'script config should be an array containing strings and arrays of strings' - end - end - - describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid - end - end - end - end -end diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index cebe8984741..f8754d7e124 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -175,27 +175,35 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do end end - context "when duplicate 'include' is defined" do + context "when duplicate 'include's are defined" do + let(:values) do + { include: [ + { 'local' => local_file }, + { 'local' => local_file } + ], + image: 'ruby:2.7' } + end + + it 'does not raise an exception' do + expect { subject }.not_to raise_error + end + end + + context 'when passing max number of files' do let(:values) do { include: [ { 'local' => local_file }, - { 'local' => local_file } + { 'remote' => remote_url } ], image: 'ruby:2.7' } end - it 'raises an exception' do - expect { subject }.to raise_error(described_class::DuplicateIncludesError) + before do + stub_const("#{described_class}::MAX_INCLUDES", 2) end - context 'when including multiple files from a project' do - let(:values) do - { include: { project: project.full_path, file: [local_file, local_file] } } - end - - it 'raises an exception' do - expect { subject }.to raise_error(described_class::DuplicateIncludesError) - end + it 'does not raise an exception' do + expect { subject }.not_to raise_error end end diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb index 091bd3b07e6..e2bb55f3854 100644 --- a/spec/lib/gitlab/ci/config/external/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb @@ -45,7 +45,7 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['Dockerfile']) } before do - project.repository.create_file(project.owner, 'Dockerfile', "commit", message: 'test', branch_name: "master") + project.repository.create_file(project.first_owner, 'Dockerfile', "commit", message: 'test', branch_name: "master") end it { is_expected.to eq(true) } diff --git a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb index a29471706cc..1cc8b462224 100644 --- a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb +++ b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true require 'fast_spec_helper' -require 'support/helpers/stubbed_feature' -require 'support/helpers/stub_feature_flags' RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do - include StubFeatureFlags - describe '.applies_to?' do subject { described_class.applies_to?(config) } diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 1b3e8a2ce4a..05ff1f3618b 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -462,7 +462,7 @@ RSpec.describe Gitlab::Ci::Config do expect(project.repository).to receive(:blob_data_at) .with('eeff1122', local_location) - described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122', user: user) + described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122', user: user, pipeline: pipeline) end end @@ -470,7 +470,7 @@ RSpec.describe Gitlab::Ci::Config do it 'is using latest SHA on the default branch' do expect(project.repository).to receive(:root_ref_sha) - described_class.new(gitlab_ci_yml, project: project, sha: nil, user: user) + described_class.new(gitlab_ci_yml, project: project, sha: nil, user: user, pipeline: pipeline) end end end diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb index 1e433d7854a..747ff13c840 100644 --- a/spec/lib/gitlab/ci/lint_spec.rb +++ b/spec/lib/gitlab/ci/lint_spec.rb @@ -7,9 +7,10 @@ RSpec.describe Gitlab::Ci::Lint do let_it_be(:user) { create(:user) } let(:lint) { described_class.new(project: project, current_user: user) } + let(:ref) { project.default_branch } describe '#validate' do - subject { lint.validate(content, dry_run: dry_run) } + subject { lint.validate(content, dry_run: dry_run, ref: ref) } shared_examples 'content is valid' do let(:content) do @@ -251,6 +252,29 @@ RSpec.describe Gitlab::Ci::Lint do end end + context 'when using a ref other than the default branch' do + let(:ref) { 'feature' } + let(:content) do + <<~YAML + build: + stage: build + script: echo 1 + rules: + - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" + test: + stage: test + script: echo 2 + rules: + - if: "$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH" + YAML + end + + it 'includes only jobs that are excluded on the default branch' do + expect(subject.jobs.size).to eq(1) + expect(subject.jobs[0][:name]).to eq('test') + end + end + it_behaves_like 'sets merged yaml' include_context 'advanced validations' do @@ -298,4 +322,102 @@ RSpec.describe Gitlab::Ci::Lint do end end end + + context 'pipeline logger' do + let(:counters) do + { + 'count' => a_kind_of(Numeric), + 'avg' => a_kind_of(Numeric), + 'max' => a_kind_of(Numeric), + 'min' => a_kind_of(Numeric) + } + end + + let(:loggable_data) do + { + 'class' => 'Gitlab::Ci::Pipeline::Logger', + 'config_build_context_duration_s' => counters, + 'config_build_variables_duration_s' => counters, + 'config_compose_duration_s' => counters, + 'config_expand_duration_s' => counters, + 'config_external_process_duration_s' => counters, + 'config_stages_inject_duration_s' => counters, + 'config_tags_resolve_duration_s' => counters, + 'config_yaml_extend_duration_s' => counters, + 'config_yaml_load_duration_s' => counters, + 'pipeline_creation_caller' => 'Gitlab::Ci::Lint', + 'pipeline_creation_service_duration_s' => a_kind_of(Numeric), + 'pipeline_persisted' => false, + 'pipeline_source' => 'unknown', + 'project_id' => project&.id, + 'yaml_process_duration_s' => counters + } + end + + let(:content) do + <<~YAML + build: + script: echo + YAML + end + + subject(:validate) { lint.validate(content, dry_run: false) } + + before do + project&.add_developer(user) + end + + context 'when the duration is under the threshold' do + it 'does not create a log entry' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + + validate + end + end + + context 'when the durations exceeds the threshold' do + let(:timer) do + proc do + @timer = @timer.to_i + 30 + end + end + + before do + allow(Gitlab::Ci::Pipeline::Logger) + .to receive(:current_monotonic_time) { timer.call } + end + + it 'creates a log entry' do + expect(Gitlab::AppJsonLogger).to receive(:info).with(loggable_data) + + validate + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(ci_pipeline_creation_logger: false) + end + + it 'does not create a log entry' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + + validate + end + end + + context 'when project is not provided' do + let(:project) { nil } + + let(:project_nil_loggable_data) do + loggable_data.except('project_id') + end + + it 'creates a log entry without project_id' do + expect(Gitlab::AppJsonLogger).to receive(:info).with(project_nil_loggable_data) + + validate + end + end + end + end end diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb index c49673f5a4a..7eec78ff186 100644 --- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb @@ -40,60 +40,142 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do allow(validator_class).to receive(:new).and_call_original end - context 'when the validate flag is set as `false`' do - let(:validate) { false } + context 'when enforce_security_report_validation is enabled' do + before do + stub_feature_flags(enforce_security_report_validation: true) + end - it 'does not run the validation logic' do - parse_report + context 'when the validate flag is set as `true`' do + let(:validate) { true } - expect(validator_class).not_to have_received(:new) - end - end + it 'instantiates the validator with correct params' do + parse_report - context 'when the validate flag is set as `true`' do - let(:validate) { true } - let(:valid?) { false } + expect(validator_class).to have_received(:new).with(report.type, {}) + end - before do - allow_next_instance_of(validator_class) do |instance| - allow(instance).to receive(:valid?).and_return(valid?) - allow(instance).to receive(:errors).and_return(['foo']) + context 'when the report data is valid according to the schema' do + let(:valid?) { true } + + before do + allow_next_instance_of(validator_class) do |instance| + allow(instance).to receive(:valid?).and_return(valid?) + allow(instance).to receive(:errors).and_return([]) + end + + allow(parser).to receive_messages(create_scanner: true, create_scan: true) + end + + it 'does not add errors to the report' do + expect { parse_report }.not_to change { report.errors }.from([]) + end + + it 'adds the schema validation status to the report' do + parse_report + + expect(report.schema_validation_status).to eq(:valid_schema) + end + + it 'keeps the execution flow as normal' do + parse_report + + expect(parser).to have_received(:create_scanner) + expect(parser).to have_received(:create_scan) + end end - allow(parser).to receive_messages(create_scanner: true, create_scan: true) - end + context 'when the report data is not valid according to the schema' do + let(:valid?) { false } - it 'instantiates the validator with correct params' do - parse_report + before do + allow_next_instance_of(validator_class) do |instance| + allow(instance).to receive(:valid?).and_return(valid?) + allow(instance).to receive(:errors).and_return(['foo']) + end - expect(validator_class).to have_received(:new).with(report.type, {}) - end + allow(parser).to receive_messages(create_scanner: true, create_scan: true) + end + + it 'adds errors to the report' do + expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }]) + end + + it 'adds the schema validation status to the report' do + parse_report - context 'when the report data is not valid according to the schema' do - it 'adds errors to the report' do - expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }]) + expect(report.schema_validation_status).to eq(:invalid_schema) + end + + it 'does not try to create report entities' do + parse_report + + expect(parser).not_to have_received(:create_scanner) + expect(parser).not_to have_received(:create_scan) + end end + end + end + + context 'when enforce_security_report_validation is disabled' do + before do + stub_feature_flags(enforce_security_report_validation: false) + end + + context 'when the validate flag is set as `false`' do + let(:validate) { false } - it 'does not try to create report entities' do + it 'does not run the validation logic' do parse_report - expect(parser).not_to have_received(:create_scanner) - expect(parser).not_to have_received(:create_scan) + expect(validator_class).not_to have_received(:new) end end - context 'when the report data is valid according to the schema' do - let(:valid?) { true } + context 'when the validate flag is set as `true`' do + let(:validate) { true } + let(:valid?) { false } - it 'does not add errors to the report' do - expect { parse_report }.not_to change { report.errors }.from([]) + before do + allow_next_instance_of(validator_class) do |instance| + allow(instance).to receive(:valid?).and_return(valid?) + allow(instance).to receive(:errors).and_return(['foo']) + end + + allow(parser).to receive_messages(create_scanner: true, create_scan: true) end - it 'keeps the execution flow as normal' do + it 'instantiates the validator with correct params' do parse_report - expect(parser).to have_received(:create_scanner) - expect(parser).to have_received(:create_scan) + expect(validator_class).to have_received(:new).with(report.type, {}) + end + + context 'when the report data is not valid according to the schema' do + it 'adds errors to the report' do + expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }]) + end + + it 'does not try to create report entities' do + parse_report + + expect(parser).not_to have_received(:create_scanner) + expect(parser).not_to have_received(:create_scan) + end + end + + context 'when the report data is valid according to the schema' do + let(:valid?) { true } + + it 'does not add errors to the report' do + expect { parse_report }.not_to change { report.errors }.from([]) + end + + it 'keeps the execution flow as normal' do + parse_report + + expect(parser).to have_received(:create_scanner) + expect(parser).to have_received(:create_scan) + end end end end diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb index 4ca8f74e57f..82fa11d5f98 100644 --- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb +++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb @@ -99,6 +99,19 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do 'Some failure' end + context 'and has failure with no message but has system-err' do + let(:testcase_content) do + <<-EOF.strip_heredoc + <failure></failure> + <system-err>Some failure</system-err> + EOF + end + + it_behaves_like '<testcase> XML parser', + ::Gitlab::Ci::Reports::TestCase::STATUS_FAILED, + 'Some failure' + end + context 'and has error' do let(:testcase_content) { '<error>Some error</error>' } @@ -107,6 +120,19 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do 'Some error' end + context 'and has error with no message but has system-err' do + let(:testcase_content) do + <<-EOF.strip_heredoc + <error></error> + <system-err>Some error</system-err> + EOF + end + + it_behaves_like '<testcase> XML parser', + ::Gitlab::Ci::Reports::TestCase::STATUS_ERROR, + 'Some error' + end + context 'and has skipped' do let(:testcase_content) { '<skipped/>' } diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb index 0a592395c3a..375841ce236 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb @@ -47,18 +47,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments do expect(job.deployment).to be_nil end end - - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it 'does not create a deployment record' do - expect { subject }.not_to change { Deployment.count } - - expect(job.deployment).to be_nil - end - end end context 'when a pipeline contains a teardown job' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb index 253928e1a19..6a7d9b58a05 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb @@ -57,18 +57,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do expect(job.persisted_environment).to be_nil end end - - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it 'does not create any environments' do - expect { subject }.not_to change { Environment.count } - - expect(job.persisted_environment).to be_nil - end - end end context 'when a pipeline contains a teardown job' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb index 87df5a3e21b..571455d6279 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb @@ -60,18 +60,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureResourceGroups do expect(job.resource_group).to be_nil end end - - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it 'does not create any resource groups' do - expect { subject }.not_to change { Ci::ResourceGroup.count } - - expect(job.resource_group).to be_nil - end - end end context 'when a pipeline does not contain a job that requires a resource group' do diff --git a/spec/lib/gitlab/ci/pipeline/logger_spec.rb b/spec/lib/gitlab/ci/pipeline/logger_spec.rb index a488bc184f8..f31361431f2 100644 --- a/spec/lib/gitlab/ci/pipeline/logger_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/logger_spec.rb @@ -47,13 +47,15 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do end def loggable_data(count:, db_count: nil) - keys = %w[ + database_name = Ci::ApplicationRecord.connection.pool.db_config.name + + keys = %W[ expensive_operation_duration_s expensive_operation_db_count expensive_operation_db_primary_count expensive_operation_db_primary_duration_s - expensive_operation_db_main_count - expensive_operation_db_main_duration_s + expensive_operation_db_#{database_name}_count + expensive_operation_db_#{database_name}_duration_s ] data = keys.each.with_object({}) do |key, accumulator| @@ -75,7 +77,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do end context 'with a single query' do - let(:operation) { -> { Project.count } } + let(:operation) { -> { Ci::Pipeline.count } } it { is_expected.to eq(operation.call) } @@ -201,6 +203,35 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do expect(commit).to be_truthy end end + + context 'when project is not passed and pipeline is not persisted' do + let(:project) {} + let(:pipeline) { build(:ci_pipeline) } + + let(:loggable_data) do + { + 'class' => described_class.name.to_s, + 'pipeline_persisted' => false, + 'pipeline_creation_service_duration_s' => a_kind_of(Numeric), + 'pipeline_creation_caller' => 'source', + 'pipeline_save_duration_s' => { + 'avg' => 60, 'count' => 1, 'max' => 60, 'min' => 60 + }, + 'pipeline_creation_duration_s' => { + 'avg' => 20, 'count' => 2, 'max' => 30, 'min' => 10 + } + } + end + + it 'logs to application.json' do + expect(Gitlab::AppJsonLogger) + .to receive(:info) + .with(a_hash_including(loggable_data)) + .and_call_original + + expect(commit).to be_truthy + end + end end context 'when the feature flag is disabled' do diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 2f9fcd7caac..49505d397c2 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -411,171 +411,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do describe '#to_resource' do subject { seed_build.to_resource } - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - context 'when job is Ci::Build' do - it { is_expected.to be_a(::Ci::Build) } - it { is_expected.to be_valid } - - shared_examples_for 'deployment job' do - it 'returns a job with deployment' do - expect { subject }.to change { Environment.count }.by(1) - - expect(subject.deployment).not_to be_nil - expect(subject.deployment.deployable).to eq(subject) - expect(subject.deployment.environment.name).to eq(expected_environment_name) - end - end - - shared_examples_for 'non-deployment job' do - it 'returns a job without deployment' do - expect(subject.deployment).to be_nil - end - end - - shared_examples_for 'ensures environment existence' do - it 'has environment' do - expect { subject }.to change { Environment.count }.by(1) - - expect(subject).to be_has_environment - expect(subject.environment).to eq(environment_name) - expect(subject.metadata.expanded_environment_name).to eq(expected_environment_name) - expect(Environment.exists?(name: expected_environment_name)).to eq(true) - end - end - - shared_examples_for 'ensures environment inexistence' do - it 'does not have environment' do - expect { subject }.not_to change { Environment.count } - - expect(subject).not_to be_has_environment - expect(subject.environment).to be_nil - expect(subject.metadata&.expanded_environment_name).to be_nil - expect(Environment.exists?(name: expected_environment_name)).to eq(false) - end - end - - context 'when job deploys to production' do - let(:environment_name) { 'production' } - let(:expected_environment_name) { 'production' } - let(:attributes) { { name: 'deploy', ref: 'master', environment: 'production' } } - - it_behaves_like 'deployment job' - it_behaves_like 'ensures environment existence' - - context 'when create_deployment_in_separate_transaction feature flag is enabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: true) - end - - it 'does not create any deployments nor environments' do - expect(subject.deployment).to be_nil - expect(Environment.count).to eq(0) - expect(Deployment.count).to eq(0) - end - end - - context 'when the environment name is invalid' do - let(:attributes) { { name: 'deploy', ref: 'master', environment: '!!!' } } - - it 'fails the job with a failure reason and does not create an environment' do - expect(subject).to be_failed - expect(subject).to be_environment_creation_failure - expect(subject.metadata.expanded_environment_name).to be_nil - expect(Environment.exists?(name: expected_environment_name)).to eq(false) - end - end - end - - context 'when job starts a review app' do - let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' } - let(:expected_environment_name) { "review/#{pipeline.ref}" } - - let(:attributes) do - { - name: 'deploy', ref: 'master', environment: environment_name, - options: { environment: { name: environment_name } } - } - end - - it_behaves_like 'deployment job' - it_behaves_like 'ensures environment existence' - end - - context 'when job stops a review app' do - let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' } - let(:expected_environment_name) { "review/#{pipeline.ref}" } - - let(:attributes) do - { - name: 'deploy', ref: 'master', environment: environment_name, - options: { environment: { name: environment_name, action: 'stop' } } - } - end - - it 'returns a job without deployment' do - expect(subject.deployment).to be_nil - end - - it_behaves_like 'non-deployment job' - it_behaves_like 'ensures environment existence' - end - - context 'when job belongs to a resource group' do - let(:resource_group) { 'iOS' } - let(:attributes) { { name: 'rspec', ref: 'master', resource_group_key: resource_group, environment: 'production' }} - - it 'returns a job with resource group' do - expect(subject.resource_group).not_to be_nil - expect(subject.resource_group.key).to eq('iOS') - expect(Ci::ResourceGroup.count).to eq(1) - end - - context 'when create_deployment_in_separate_transaction feature flag is enabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: true) - end - - it 'does not create any resource groups' do - expect(subject.resource_group).to be_nil - expect(Ci::ResourceGroup.count).to eq(0) - end - end - - context 'when resource group has $CI_ENVIRONMENT_NAME in it' do - let(:resource_group) { 'test/$CI_ENVIRONMENT_NAME' } - - it 'expands environment name' do - expect(subject.resource_group.key).to eq('test/production') - end - end - end - end - - context 'when job is a bridge' do - let(:base_attributes) do - { - name: 'rspec', ref: 'master', options: { trigger: 'my/project' }, scheduling_type: :stage - } - end - - let(:attributes) { base_attributes } - - it { is_expected.to be_a(::Ci::Bridge) } - it { is_expected.to be_valid } - - context 'when job belongs to a resource group' do - let(:attributes) { base_attributes.merge(resource_group_key: 'iOS') } - - it 'returns a job with resource group' do - expect(subject.resource_group).not_to be_nil - expect(subject.resource_group.key).to eq('iOS') - end - end - end - it 'memoizes a resource object' do expect(subject.object_id).to eq seed_build.to_resource.object_id end diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb index 3b0eaffc54e..f4b47893805 100644 --- a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb @@ -85,6 +85,9 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do let(:info) { build(:codequality_degradation, :info) } let(:major_2) { build(:codequality_degradation, :major) } let(:critical) { build(:codequality_degradation, :critical) } + let(:uppercase_major) { build(:codequality_degradation, severity: 'MAJOR') } + let(:unknown) { build(:codequality_degradation, severity: 'unknown') } + let(:codequality_report) { described_class.new } before do @@ -94,6 +97,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do codequality_report.add_degradation(major_2) codequality_report.add_degradation(info) codequality_report.add_degradation(critical) + codequality_report.add_degradation(unknown) codequality_report.sort_degradations! end @@ -105,8 +109,30 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do major, major_2, minor, - info + info, + unknown ]) end + + context 'with non-existence and uppercase severities' do + let(:other_report) { described_class.new } + let(:non_existent) { build(:codequality_degradation, severity: 'non-existent') } + + before do + other_report.add_degradation(blocker) + other_report.add_degradation(uppercase_major) + other_report.add_degradation(minor) + other_report.add_degradation(non_existent) + end + + it 'sorts unknown last' do + expect(other_report.degradations.values).to eq([ + blocker, + uppercase_major, + minor, + non_existent + ]) + end + end end end diff --git a/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb b/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb index 784c1183320..2320f011cf5 100644 --- a/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/finding_key_spec.rb @@ -6,36 +6,47 @@ RSpec.describe Gitlab::Ci::Reports::Security::FindingKey do using RSpec::Parameterized::TableSyntax describe '#==' do - where(:location_fp_1, :location_fp_2, :identifier_fp_1, :identifier_fp_2, :equals?) do - nil | 'different location fp' | 'identifier fp' | 'different identifier fp' | false - 'location fp' | nil | 'identifier fp' | 'different identifier fp' | false - 'location fp' | 'different location fp' | nil | 'different identifier fp' | false - 'location fp' | 'different location fp' | 'identifier fp' | nil | false - nil | nil | 'identifier fp' | 'identifier fp' | false - 'location fp' | 'location fp' | nil | nil | false - nil | nil | nil | nil | false - 'location fp' | 'different location fp' | 'identifier fp' | 'different identifier fp' | false - 'location fp' | 'different location fp' | 'identifier fp' | 'identifier fp' | false - 'location fp' | 'location fp' | 'identifier fp' | 'different identifier fp' | false - 'location fp' | 'location fp' | 'identifier fp' | 'identifier fp' | true - end - - with_them do - let(:finding_key_1) do - build(:ci_reports_security_finding_key, - location_fingerprint: location_fp_1, - identifier_fingerprint: identifier_fp_1) + context 'when the comparison is done between FindingKey instances' do + where(:location_fp_1, :location_fp_2, :identifier_fp_1, :identifier_fp_2, :equals?) do + nil | 'different location fp' | 'identifier fp' | 'different identifier fp' | false + 'location fp' | nil | 'identifier fp' | 'different identifier fp' | false + 'location fp' | 'different location fp' | nil | 'different identifier fp' | false + 'location fp' | 'different location fp' | 'identifier fp' | nil | false + nil | nil | 'identifier fp' | 'identifier fp' | false + 'location fp' | 'location fp' | nil | nil | false + nil | nil | nil | nil | false + 'location fp' | 'different location fp' | 'identifier fp' | 'different identifier fp' | false + 'location fp' | 'different location fp' | 'identifier fp' | 'identifier fp' | false + 'location fp' | 'location fp' | 'identifier fp' | 'different identifier fp' | false + 'location fp' | 'location fp' | 'identifier fp' | 'identifier fp' | true end - let(:finding_key_2) do - build(:ci_reports_security_finding_key, - location_fingerprint: location_fp_2, - identifier_fingerprint: identifier_fp_2) + with_them do + let(:finding_key_1) do + build(:ci_reports_security_finding_key, + location_fingerprint: location_fp_1, + identifier_fingerprint: identifier_fp_1) + end + + let(:finding_key_2) do + build(:ci_reports_security_finding_key, + location_fingerprint: location_fp_2, + identifier_fingerprint: identifier_fp_2) + end + + subject { finding_key_1 == finding_key_2 } + + it { is_expected.to be(equals?) } end + end + + context 'when the comparison is not done between FindingKey instances' do + let(:finding_key) { build(:ci_reports_security_finding_key) } + let(:uuid) { SecureRandom.uuid } - subject { finding_key_1 == finding_key_2 } + subject { finding_key == uuid } - it { is_expected.to be(equals?) } + it { is_expected.to be_falsey } end end end diff --git a/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb index f8df2266689..8204b104832 100644 --- a/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb @@ -8,7 +8,7 @@ RSpec.describe '5-Minute-Production-App.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_branch) { default_branch } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } diff --git a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb index ca6f6872f89..27de8324206 100644 --- a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Deploy-ECS.gitlab-ci.yml' do let(:default_branch) { project.default_branch_or_main } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb index bd701aec8fc..21052f03cb8 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Jobs/Build.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { project.owner } + let_it_be(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb index 64243f2d205..d88d9782021 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { project.owner } + let_it_be(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb index 789f694b4b4..b657f73fa77 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb @@ -29,7 +29,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project, refind: true) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) } @@ -66,6 +66,11 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do expect(build_names).not_to include('review') end + it 'when CI_DEPLOY_FREEZE is present' do + create(:ci_variable, project: project, key: 'CI_DEPLOY_FREEZE', value: 'true') + expect(build_names).to eq %w(placeholder) + end + it 'when CANARY_ENABLED' do create(:ci_variable, project: project, key: 'CANARY_ENABLED', value: 'true') diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb index b9256ece78b..0f97bc06a4e 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { project.owner } + let_it_be(:user) { project.first_owner } let(:default_branch) { 'main' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb index db9d7496251..a92a8397e96 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do describe 'the created pipeline' do let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { project.owner } + let_it_be(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb index 4685d843ce0..5e9224cebd9 100644 --- a/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Terraform/Base.gitlab-ci.yml' do let(:default_branch) { 'master' } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb index e35f2eabe8e..0ab81f97f20 100644 --- a/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Terraform/base_latest_gitlab_ci_yaml_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Terraform/Base.latest.gitlab-ci.yml' do let(:default_branch) { 'master' } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb index 004261bc617..d6c7cd32f79 100644 --- a/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb @@ -20,7 +20,7 @@ RSpec.describe 'Verify/Load-Performance-Testing.gitlab-ci.yml' do describe 'the created pipeline' do let(:project) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:default_branch) { 'master' } let(:pipeline_ref) { default_branch } diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb index 64ef6ecd7f8..6a4be1fa072 100644 --- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do describe 'the created pipeline' do let(:pipeline_branch) { default_branch } let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } @@ -276,7 +276,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do with_them do let(:project) { create(:project, :custom_repo, files: files) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: default_branch ) } let(:pipeline) { service.execute(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb index 3d97b47473d..de94eec09fe 100644 --- a/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/flutter_gitlab_ci_yaml_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'Flutter.gitlab-ci.yml' do describe 'the created pipeline' do let(:pipeline_branch) { 'master' } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb index c7dbbea4622..ebf52e6d65a 100644 --- a/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'Kaniko.gitlab-ci.yml' do describe 'the created pipeline' do let(:pipeline_branch) { 'master' } let(:project) { create(:project, :custom_repo, files: { 'Dockerfile' => 'FROM alpine:latest' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/npm_spec.rb b/spec/lib/gitlab/ci/templates/npm_spec.rb index ea954690133..d86a3a67823 100644 --- a/spec/lib/gitlab/ci/templates/npm_spec.rb +++ b/spec/lib/gitlab/ci/templates/npm_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'npm.gitlab-ci.yml' do let(:repo_files) { { 'package.json' => '{}', 'README.md' => '' } } let(:modified_files) { %w[package.json] } let(:project) { create(:project, :custom_repo, files: repo_files) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:pipeline_branch) { project.default_branch } let(:pipeline_tag) { 'v1.2.1' } let(:pipeline_ref) { pipeline_branch } diff --git a/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb index 936cd6ac8aa..346ab9f7af7 100644 --- a/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/terraform_gitlab_ci_yaml_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'Terraform.gitlab-ci.yml' do let(:default_branch) { project.default_branch_or_main } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb index fd5d5d6af7f..6c06403adff 100644 --- a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'Terraform.latest.gitlab-ci.yml' do let(:default_branch) { project.default_branch_or_main } let(:pipeline_branch) { default_branch } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } let(:pipeline) { service.execute!(:push).payload } let(:build_names) { pipeline.builds.pluck(:name) } diff --git a/spec/lib/gitlab/ci/variables/builder/instance_spec.rb b/spec/lib/gitlab/ci/variables/builder/instance_spec.rb new file mode 100644 index 00000000000..7abda2bd615 --- /dev/null +++ b/spec/lib/gitlab/ci/variables/builder/instance_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Variables::Builder::Instance do + let_it_be(:variable) { create(:ci_instance_variable, protected: false) } + let_it_be(:protected_variable) { create(:ci_instance_variable, protected: true) } + + let(:builder) { described_class.new } + + describe '#secret_variables' do + let(:variable_item) { item(variable) } + let(:protected_variable_item) { item(protected_variable) } + + subject do + builder.secret_variables(protected_ref: protected_ref) + end + + context 'when the ref is protected' do + let(:protected_ref) { true } + + it 'contains all the variables' do + is_expected.to contain_exactly(variable_item, protected_variable_item) + end + end + + context 'when the ref is not protected' do + let(:protected_ref) { false } + + it 'contains only unprotected variables' do + is_expected.to contain_exactly(variable_item) + end + end + end + + def item(variable) + Gitlab::Ci::Variables::Collection::Item.fabricate(variable) + end +end diff --git a/spec/lib/gitlab/ci/variables/builder/project_spec.rb b/spec/lib/gitlab/ci/variables/builder/project_spec.rb new file mode 100644 index 00000000000..b64b6ea98e2 --- /dev/null +++ b/spec/lib/gitlab/ci/variables/builder/project_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Variables::Builder::Project do + let_it_be(:project) { create(:project, :repository) } + + let(:builder) { described_class.new(project) } + + describe '#secret_variables' do + let(:environment) { '*' } + let(:protected_ref) { false } + + let_it_be(:variable) do + create(:ci_variable, + value: 'secret', + project: project) + end + + let_it_be(:protected_variable) do + create(:ci_variable, :protected, + value: 'protected', + project: project) + end + + let(:variable_item) { item(variable) } + let(:protected_variable_item) { item(protected_variable) } + + subject do + builder.secret_variables( + environment: environment, + protected_ref: protected_ref) + end + + context 'when the ref is protected' do + let(:protected_ref) { true } + + it 'contains all the variables' do + is_expected.to contain_exactly(variable_item, protected_variable_item) + end + end + + context 'when the ref is not protected' do + let(:protected_ref) { false } + + it 'contains only the unprotected variables' do + is_expected.to contain_exactly(variable_item) + end + end + + context 'when environment name is specified' do + let(:environment) { 'review/name' } + + before do + Ci::Variable.update_all(environment_scope: environment_scope) + end + + context 'when environment scope is exactly matched' do + let(:environment_scope) { 'review/name' } + + it { is_expected.to contain_exactly(variable_item) } + end + + context 'when environment scope is matched by wildcard' do + let(:environment_scope) { 'review/*' } + + it { is_expected.to contain_exactly(variable_item) } + end + + context 'when environment scope does not match' do + let(:environment_scope) { 'review/*/special' } + + it { is_expected.not_to contain_exactly(variable_item) } + end + + context 'when environment scope has _' do + let(:environment_scope) { '*_*' } + + it 'does not treat it as wildcard' do + is_expected.not_to contain_exactly(variable_item) + end + end + + context 'when environment name contains underscore' do + let(:environment) { 'foo_bar/test' } + let(:environment_scope) { 'foo_bar/*' } + + it 'matches literally for _' do + is_expected.to contain_exactly(variable_item) + end + end + + # The environment name and scope cannot have % at the moment, + # but we're considering relaxing it and we should also make sure + # it doesn't break in case some data sneaked in somehow as we're + # not checking this integrity in database level. + context 'when environment scope has %' do + let(:environment_scope) { '*%*' } + + it 'does not treat it as wildcard' do + is_expected.not_to contain_exactly(variable_item) + end + end + + context 'when environment name contains a percent' do + let(:environment) { 'foo%bar/test' } + let(:environment_scope) { 'foo%bar/*' } + + it 'matches literally for _' do + is_expected.to contain_exactly(variable_item) + end + end + end + + context 'when variables with the same name have different environment scopes' do + let(:environment) { 'review/name' } + + let_it_be(:partially_matched_variable) do + create(:ci_variable, + key: variable.key, + value: 'partial', + environment_scope: 'review/*', + project: project) + end + + let_it_be(:perfectly_matched_variable) do + create(:ci_variable, + key: variable.key, + value: 'prefect', + environment_scope: 'review/name', + project: project) + end + + it 'puts variables matching environment scope more in the end' do + variables_collection = Gitlab::Ci::Variables::Collection.new([ + variable, + partially_matched_variable, + perfectly_matched_variable + ]).to_runner_variables + + expect(subject.to_runner_variables).to eq(variables_collection) + end + end + end + + def item(variable) + Gitlab::Ci::Variables::Collection::Item.fabricate(variable) + end +end diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb index 8a87cbe45c1..6e144d62ac0 100644 --- a/spec/lib/gitlab/ci/variables/builder_spec.rb +++ b/spec/lib/gitlab/ci/variables/builder_spec.rb @@ -3,10 +3,11 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Variables::Builder do - let_it_be(:project) { create(:project, :repository) } - let_it_be(:pipeline) { create(:ci_pipeline, project: project) } - let_it_be(:user) { project.owner } - let_it_be(:job) do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :repository, namespace: group) } + let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:job) do create(:ci_build, pipeline: pipeline, user: user, @@ -153,7 +154,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder do before do allow(builder).to receive(:predefined_variables) { [var('A', 1), var('B', 1)] } - allow(project).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] } + allow(pipeline.project).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] } allow(pipeline).to receive(:predefined_variables) { [var('C', 3), var('D', 3)] } allow(job).to receive(:runner) { double(predefined_variables: [var('D', 4), var('E', 4)]) } allow(builder).to receive(:kubernetes_variables) { [var('E', 5), var('F', 5)] } @@ -201,4 +202,240 @@ RSpec.describe Gitlab::Ci::Variables::Builder do end end end + + describe '#user_variables' do + context 'with user' do + subject { builder.user_variables(user).to_hash } + + let(:expected_variables) do + { + 'GITLAB_USER_EMAIL' => user.email, + 'GITLAB_USER_ID' => user.id.to_s, + 'GITLAB_USER_LOGIN' => user.username, + 'GITLAB_USER_NAME' => user.name + } + end + + it { is_expected.to eq(expected_variables) } + end + + context 'without user' do + subject { builder.user_variables(nil).to_hash } + + it { is_expected.to be_empty } + end + end + + describe '#kubernetes_variables' do + let(:service) { double(execute: template) } + let(:template) { double(to_yaml: 'example-kubeconfig', valid?: template_valid) } + let(:template_valid) { true } + + subject { builder.kubernetes_variables(job) } + + before do + allow(Ci::GenerateKubeconfigService).to receive(:new).with(job).and_return(service) + end + + it { is_expected.to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) } + + context 'generated config is invalid' do + let(:template_valid) { false } + + it { is_expected.not_to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) } + end + end + + describe '#deployment_variables' do + let(:environment) { 'production' } + let(:kubernetes_namespace) { 'namespace' } + let(:project_variables) { double } + + subject { builder.deployment_variables(environment: environment, job: job) } + + before do + allow(job).to receive(:expanded_kubernetes_namespace) + .and_return(kubernetes_namespace) + + allow(project).to receive(:deployment_variables) + .with(environment: environment, kubernetes_namespace: kubernetes_namespace) + .and_return(project_variables) + end + + context 'environment is nil' do + let(:environment) { nil } + + it { is_expected.to be_empty } + end + end + + shared_examples "secret CI variables" do + context 'when ref is branch' do + context 'when ref is protected' do + before do + create(:protected_branch, :developers_can_merge, name: job.ref, project: project) + end + + it { is_expected.to contain_exactly(protected_variable_item, unprotected_variable_item) } + end + + context 'when ref is not protected' do + it { is_expected.to contain_exactly(unprotected_variable_item) } + end + end + + context 'when ref is tag' do + before do + job.update!(ref: 'v1.1.0', tag: true) + pipeline.update!(ref: 'v1.1.0', tag: true) + end + + context 'when ref is protected' do + before do + create(:protected_tag, project: project, name: 'v*') + end + + it { is_expected.to contain_exactly(protected_variable_item, unprotected_variable_item) } + end + + context 'when ref is not protected' do + it { is_expected.to contain_exactly(unprotected_variable_item) } + end + end + + context 'when ref is merge request' do + let_it_be(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project) } + let_it_be(:pipeline) { merge_request.pipelines_for_merge_request.first } + let_it_be(:job) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline) } + + context 'when ref is protected' do + before do + create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project) + end + + it 'does not return protected variables as it is not supported for merge request pipelines' do + is_expected.to contain_exactly(unprotected_variable_item) + end + end + + context 'when ref is not protected' do + it { is_expected.to contain_exactly(unprotected_variable_item) } + end + end + end + + describe '#secret_instance_variables' do + subject { builder.secret_instance_variables } + + let_it_be(:protected_variable) { create(:ci_instance_variable, protected: true) } + let_it_be(:unprotected_variable) { create(:ci_instance_variable, protected: false) } + + let(:protected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(protected_variable) } + let(:unprotected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(unprotected_variable) } + + include_examples "secret CI variables" + end + + describe '#secret_group_variables' do + subject { builder.secret_group_variables(ref: job.git_ref, environment: job.expanded_environment_name) } + + let_it_be(:protected_variable) { create(:ci_group_variable, protected: true, group: group) } + let_it_be(:unprotected_variable) { create(:ci_group_variable, protected: false, group: group) } + + let(:protected_variable_item) { protected_variable } + let(:unprotected_variable_item) { unprotected_variable } + + include_examples "secret CI variables" + end + + describe '#secret_project_variables' do + let_it_be(:protected_variable) { create(:ci_variable, protected: true, project: project) } + let_it_be(:unprotected_variable) { create(:ci_variable, protected: false, project: project) } + + let(:ref) { job.git_ref } + let(:environment) { job.expanded_environment_name } + + subject { builder.secret_project_variables(ref: ref, environment: environment) } + + context 'with ci_variables_builder_memoize_secret_variables disabled' do + before do + stub_feature_flags(ci_variables_builder_memoize_secret_variables: false) + end + + let(:protected_variable_item) { protected_variable } + let(:unprotected_variable_item) { unprotected_variable } + + include_examples "secret CI variables" + end + + context 'with ci_variables_builder_memoize_secret_variables enabled' do + before do + stub_feature_flags(ci_variables_builder_memoize_secret_variables: true) + end + + let(:protected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(protected_variable) } + let(:unprotected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(unprotected_variable) } + + include_examples "secret CI variables" + + context 'variables memoization' do + let_it_be(:scoped_variable) { create(:ci_variable, project: project, environment_scope: 'scoped') } + + let(:scoped_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(scoped_variable) } + + context 'with protected environments' do + it 'memoizes the result by environment' do + expect(pipeline.project) + .to receive(:protected_for?) + .with(pipeline.jobs_git_ref) + .once.and_return(true) + + expect_next_instance_of(described_class::Project) do |project_variables_builder| + expect(project_variables_builder) + .to receive(:secret_variables) + .with(environment: 'production', protected_ref: true) + .once + .and_call_original + end + + 2.times do + expect(builder.secret_project_variables(ref: ref, environment: 'production')) + .to contain_exactly(unprotected_variable_item, protected_variable_item) + end + end + end + + context 'with unprotected environments' do + it 'memoizes the result by environment' do + expect(pipeline.project) + .to receive(:protected_for?) + .with(pipeline.jobs_git_ref) + .once.and_return(false) + + expect_next_instance_of(described_class::Project) do |project_variables_builder| + expect(project_variables_builder) + .to receive(:secret_variables) + .with(environment: nil, protected_ref: false) + .once + .and_call_original + + expect(project_variables_builder) + .to receive(:secret_variables) + .with(environment: 'scoped', protected_ref: false) + .once + .and_call_original + end + + 2.times do + expect(builder.secret_project_variables(ref: 'other', environment: nil)) + .to contain_exactly(unprotected_variable_item) + + expect(builder.secret_project_variables(ref: 'other', environment: 'scoped')) + .to contain_exactly(unprotected_variable_item, scoped_variable_item) + end + end + end + end + end + end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 20af84ce648..5f46607b042 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -9,6 +9,10 @@ module Gitlab subject { described_class.new(config, user: nil).execute } + before do + stub_feature_flags(allow_unsafe_ruby_regexp: false) + end + shared_examples 'returns errors' do |error_message| it 'adds a message when an error is encountered' do expect(subject.errors).to include(error_message) @@ -609,13 +613,13 @@ module Gitlab context 'when it is an array of integers' do let(:only) { [1, 1] } - it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regexps' + it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regular expressions using re2 syntax' end context 'when it is invalid regex' do let(:only) { ["/*invalid/"] } - it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regexps' + it_behaves_like 'returns errors', 'jobs:rspec:only config should be an array of strings or regular expressions using re2 syntax' end end @@ -633,13 +637,13 @@ module Gitlab context 'when it is an array of integers' do let(:except) { [1, 1] } - it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regexps' + it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regular expressions using re2 syntax' end context 'when it is invalid regex' do let(:except) { ["/*invalid/"] } - it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regexps' + it_behaves_like 'returns errors', 'jobs:rspec:except config should be an array of strings or regular expressions using re2 syntax' end end end @@ -710,16 +714,16 @@ module Gitlab end end - context 'when script is array of arrays of strings' do + context 'when script is nested arrays of strings' do let(:config) do { - before_script: [["global script", "echo 1"], ["ls"], "pwd"], + before_script: [[["global script"], "echo 1"], "echo 2", ["ls"], "pwd"], test: { script: ["script"] } } end it "return commands with scripts concatenated" do - expect(subject[:options][:before_script]).to eq(["global script", "echo 1", "ls", "pwd"]) + expect(subject[:options][:before_script]).to eq(["global script", "echo 1", "echo 2", "ls", "pwd"]) end end end @@ -737,15 +741,15 @@ module Gitlab end end - context 'when script is array of arrays of strings' do + context 'when script is nested arrays of strings' do let(:config) do { - test: { script: [["script"], ["echo 1"], "ls"] } + test: { script: [[["script"], "echo 1", "echo 2"], "ls"] } } end it "return commands with scripts concatenated" do - expect(subject[:options][:script]).to eq(["script", "echo 1", "ls"]) + expect(subject[:options][:script]).to eq(["script", "echo 1", "echo 2", "ls"]) end end end @@ -790,16 +794,16 @@ module Gitlab end end - context 'when script is array of arrays of strings' do + context 'when script is nested arrays of strings' do let(:config) do { - after_script: [["global script", "echo 1"], ["ls"], "pwd"], + after_script: [[["global script"], "echo 1"], "echo 2", ["ls"], "pwd"], test: { script: ["script"] } } end it "return after_script in options" do - expect(subject[:options][:after_script]).to eq(["global script", "echo 1", "ls", "pwd"]) + expect(subject[:options][:after_script]).to eq(["global script", "echo 1", "echo 2", "ls", "pwd"]) end end end @@ -2469,40 +2473,16 @@ module Gitlab it_behaves_like 'returns errors', 'jobs:rspec:tags config should be an array of strings' end - context 'returns errors if before_script parameter is invalid' do - let(:config) { YAML.dump({ before_script: "bundle update", rspec: { script: "test" } }) } - - it_behaves_like 'returns errors', 'before_script config should be an array containing strings and arrays of strings' - end - context 'returns errors if job before_script parameter is not an array of strings' do let(:config) { YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } }) } - it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be an array containing strings and arrays of strings' - end - - context 'returns errors if job before_script parameter is multi-level nested array of strings' do - let(:config) { YAML.dump({ rspec: { script: "test", before_script: [["ls", ["pwd"]], "test"] } }) } - - it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be an array containing strings and arrays of strings' - end - - context 'returns errors if after_script parameter is invalid' do - let(:config) { YAML.dump({ after_script: "bundle update", rspec: { script: "test" } }) } - - it_behaves_like 'returns errors', 'after_script config should be an array containing strings and arrays of strings' + it_behaves_like 'returns errors', 'jobs:rspec:before_script config should be a string or a nested array of strings up to 10 levels deep' end context 'returns errors if job after_script parameter is not an array of strings' do let(:config) { YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } }) } - it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be an array containing strings and arrays of strings' - end - - context 'returns errors if job after_script parameter is multi-level nested array of strings' do - let(:config) { YAML.dump({ rspec: { script: "test", after_script: [["ls", ["pwd"]], "test"] } }) } - - it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be an array containing strings and arrays of strings' + it_behaves_like 'returns errors', 'jobs:rspec:after_script config should be a string or a nested array of strings up to 10 levels deep' end context 'returns errors if image parameter is invalid' do diff --git a/spec/lib/gitlab/cluster/lifecycle_events_spec.rb b/spec/lib/gitlab/cluster/lifecycle_events_spec.rb index 4ed68d54680..5eea78acd98 100644 --- a/spec/lib/gitlab/cluster/lifecycle_events_spec.rb +++ b/spec/lib/gitlab/cluster/lifecycle_events_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'rspec-parameterized' +require 'spec_helper' RSpec.describe Gitlab::Cluster::LifecycleEvents do # we create a new instance to ensure that we do not touch existing hooks diff --git a/spec/lib/gitlab/config/entry/factory_spec.rb b/spec/lib/gitlab/config/entry/factory_spec.rb index 260b5cf0ade..be4dfd31651 100644 --- a/spec/lib/gitlab/config/entry/factory_spec.rb +++ b/spec/lib/gitlab/config/entry/factory_spec.rb @@ -5,8 +5,8 @@ require 'spec_helper' RSpec.describe Gitlab::Config::Entry::Factory do describe '#create!' do before do - stub_const('Script', Class.new(Gitlab::Config::Entry::Node)) - Script.class_eval do + stub_const('Commands', Class.new(Gitlab::Config::Entry::Node)) + Commands.class_eval do include Gitlab::Config::Entry::Validatable validations do @@ -15,7 +15,7 @@ RSpec.describe Gitlab::Config::Entry::Factory do end end - let(:entry) { Script } + let(:entry) { Commands } let(:factory) { described_class.new(entry) } context 'when setting a concrete value' do diff --git a/spec/lib/gitlab/console_spec.rb b/spec/lib/gitlab/console_spec.rb new file mode 100644 index 00000000000..f043433b4c5 --- /dev/null +++ b/spec/lib/gitlab/console_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Gitlab::Console do + describe '.welcome!' do + context 'when running in the Rails console' do + before do + allow(Gitlab::Runtime).to receive(:console?).and_return(true) + allow(Gitlab::Metrics::BootTimeTracker.instance).to receive(:startup_time).and_return(42) + end + + shared_examples 'console messages' do + it 'prints system info' do + expect($stdout).to receive(:puts).ordered.with(include("--")) + expect($stdout).to receive(:puts).ordered.with(include("Ruby:")) + expect($stdout).to receive(:puts).ordered.with(include("GitLab:")) + expect($stdout).to receive(:puts).ordered.with(include("GitLab Shell:")) + expect($stdout).to receive(:puts).ordered.with(include("PostgreSQL:")) + expect($stdout).to receive(:puts).ordered.with(include("--")) + expect($stdout).not_to receive(:puts).ordered + + described_class.welcome! + end + end + + # This is to add line coverage, not to actually verify behavior on macOS. + context 'on darwin' do + before do + stub_const('RUBY_PLATFORM', 'x86_64-darwin-19') + end + + it_behaves_like 'console messages' + end + + it_behaves_like 'console messages' + end + + context 'when not running in the Rails console' do + before do + allow(Gitlab::Runtime).to receive(:console?).and_return(false) + end + + it 'does not print anything' do + expect($stdout).not_to receive(:puts) + + described_class.welcome! + end + end + end +end diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 46c33d7b7b2..73540a9b0f3 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -118,7 +118,7 @@ RSpec.describe Gitlab::CurrentSettings do allow(Gitlab::Runtime).to receive(:rake?).and_return(true) # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues # during the initialization phase of the test suite, so instead let's mock the internals of it - allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false) + allow(ApplicationSetting.connection).to receive(:active?).and_return(false) end context 'and no settings in cache' do @@ -150,8 +150,8 @@ RSpec.describe Gitlab::CurrentSettings do it 'fetches the settings from cache' do # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues # during the initialization phase of the test suite, so instead let's mock the internals of it - expect(ActiveRecord::Base.connection).not_to receive(:active?) - expect(ActiveRecord::Base.connection).not_to receive(:cached_table_exists?) + expect(ApplicationSetting.connection).not_to receive(:active?) + expect(ApplicationSetting.connection).not_to receive(:cached_table_exists?) expect_any_instance_of(ActiveRecord::MigrationContext).not_to receive(:needs_migration?) expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0) end @@ -159,8 +159,8 @@ RSpec.describe Gitlab::CurrentSettings do context 'and no settings in cache' do before do - allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true) - allow(ActiveRecord::Base.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true) + allow(ApplicationSetting.connection).to receive(:active?).and_return(true) + allow(ApplicationSetting.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true) end context 'with RequestStore enabled', :request_store do @@ -181,7 +181,7 @@ RSpec.describe Gitlab::CurrentSettings do context 'when ApplicationSettings does not have a primary key' do before do - allow(ActiveRecord::Base.connection).to receive(:primary_key).with('application_settings').and_return(nil) + allow(ApplicationSetting.connection).to receive(:primary_key).with('application_settings').and_return(nil) end it 'raises an exception if ApplicationSettings does not have a primary key' do diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb index 8053f5261c0..7173ea43450 100644 --- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb @@ -15,6 +15,13 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do let(:stage_summary) { described_class.new(project, **args).data } + describe '#identifier' do + it 'returns identifiers for each metric' do + identifiers = stage_summary.pluck(:identifier) + expect(identifiers).to eq(%i[issues commits deploys deployment_frequency]) + end + end + describe "#new_issues" do subject { stage_summary.first } diff --git a/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb b/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb index e96862fbc2d..66983733411 100644 --- a/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::Database::BackgroundMigration::BatchMetrics do let(:batch_metrics) { described_class.new } diff --git a/spec/lib/gitlab/database/background_migration/batch_optimizer_spec.rb b/spec/lib/gitlab/database/background_migration/batch_optimizer_spec.rb index 95863ce3765..c367f4a4493 100644 --- a/spec/lib/gitlab/database/background_migration/batch_optimizer_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batch_optimizer_spec.rb @@ -6,7 +6,11 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchOptimizer do describe '#optimize' do subject { described_class.new(migration, number_of_jobs: number_of_jobs, ema_alpha: ema_alpha).optimize! } - let(:migration) { create(:batched_background_migration, batch_size: batch_size, sub_batch_size: 100, interval: 120) } + let(:migration_params) { {} } + let(:migration) do + params = { batch_size: batch_size, sub_batch_size: 100, interval: 120 }.merge(migration_params) + create(:batched_background_migration, params) + end let(:batch_size) { 10_000 } @@ -87,6 +91,17 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchOptimizer do expect { subject }.to change { migration.reload.batch_size }.to(2_000_000) end + + context 'when max_batch_size is set' do + let(:max_batch_size) { 10000 } + let(:migration_params) { { max_batch_size: max_batch_size } } + + it 'caps the batch size at max_batch_size' do + mock_efficiency(0.7) + + expect { subject }.to change { migration.reload.batch_size }.to(max_batch_size) + end + end end context 'reaching the lower limit for the batch size' do diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb index c4364826ee2..7338ea657b9 100644 --- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb @@ -7,18 +7,85 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d describe 'associations' do it { is_expected.to belong_to(:batched_migration).with_foreign_key(:batched_background_migration_id) } + it { is_expected.to have_many(:batched_job_transition_logs).with_foreign_key(:batched_background_migration_job_id) } + end + + describe 'state machine' do + let_it_be(:job) { create(:batched_background_migration_job, :failed) } + + context 'when a job is running' do + it 'logs the transition' do + expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :running, previous_state: :failed } ) + + expect { job.run! }.to change(job, :started_at) + end + end + + context 'when a job succeed' do + let(:job) { create(:batched_background_migration_job, :running) } + + it 'logs the transition' do + expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :succeeded, previous_state: :running } ) + + job.succeed! + end + + it 'updates the finished_at' do + expect { job.succeed! }.to change(job, :finished_at).from(nil).to(Time) + end + + it 'creates a new transition log' do + job.succeed! + + transition_log = job.batched_job_transition_logs.first + + expect(transition_log.next_status).to eq('succeeded') + expect(transition_log.exception_class).to be_nil + expect(transition_log.exception_message).to be_nil + end + end + + context 'when a job fails' do + let(:job) { create(:batched_background_migration_job, :running) } + + it 'logs the transition' do + expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :failed, previous_state: :running } ) + + job.failure! + end + + it 'tracks the exception' do + expect(Gitlab::ErrorTracking).to receive(:track_exception).with(RuntimeError, { batched_job_id: job.id } ) + + job.failure!(error: RuntimeError.new) + end + + it 'updates the finished_at' do + expect { job.failure! }.to change(job, :finished_at).from(nil).to(Time) + end + + it 'creates a new transition log' do + job.failure!(error: RuntimeError.new) + + transition_log = job.batched_job_transition_logs.first + + expect(transition_log.next_status).to eq('failed') + expect(transition_log.exception_class).to eq('RuntimeError') + expect(transition_log.exception_message).to eq('RuntimeError') + end + end end describe 'scopes' do let_it_be(:fixed_time) { Time.new(2021, 04, 27, 10, 00, 00, 00) } - let_it_be(:pending_job) { create(:batched_background_migration_job, status: :pending, updated_at: fixed_time) } - let_it_be(:running_job) { create(:batched_background_migration_job, status: :running, updated_at: fixed_time) } - let_it_be(:stuck_job) { create(:batched_background_migration_job, status: :pending, updated_at: fixed_time - described_class::STUCK_JOBS_TIMEOUT) } - let_it_be(:failed_job) { create(:batched_background_migration_job, status: :failed, attempts: 1) } + let_it_be(:pending_job) { create(:batched_background_migration_job, :pending, updated_at: fixed_time) } + let_it_be(:running_job) { create(:batched_background_migration_job, :running, updated_at: fixed_time) } + let_it_be(:stuck_job) { create(:batched_background_migration_job, :pending, updated_at: fixed_time - described_class::STUCK_JOBS_TIMEOUT) } + let_it_be(:failed_job) { create(:batched_background_migration_job, :failed, attempts: 1) } - let!(:max_attempts_failed_job) { create(:batched_background_migration_job, status: :failed, attempts: described_class::MAX_ATTEMPTS) } - let!(:succeeded_job) { create(:batched_background_migration_job, status: :succeeded) } + let!(:max_attempts_failed_job) { create(:batched_background_migration_job, :failed, attempts: described_class::MAX_ATTEMPTS) } + let!(:succeeded_job) { create(:batched_background_migration_job, :succeeded) } before do travel_to fixed_time @@ -82,10 +149,10 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d subject { job.time_efficiency } let(:migration) { build(:batched_background_migration, interval: 120.seconds) } - let(:job) { build(:batched_background_migration_job, status: :succeeded, batched_migration: migration) } + let(:job) { build(:batched_background_migration_job, :succeeded, batched_migration: migration) } context 'when job has not yet succeeded' do - let(:job) { build(:batched_background_migration_job, status: :running) } + let(:job) { build(:batched_background_migration_job, :running) } it 'returns nil' do expect(subject).to be_nil @@ -130,7 +197,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end describe '#split_and_retry!' do - let!(:job) { create(:batched_background_migration_job, batch_size: 10, min_value: 6, max_value: 15, status: :failed, attempts: 3) } + let!(:job) { create(:batched_background_migration_job, :failed, batch_size: 10, min_value: 6, max_value: 15, attempts: 3) } context 'when job can be split' do before do @@ -146,7 +213,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d min_value: 6, max_value: 10, batch_size: 5, - status: 'failed', + status_name: :failed, attempts: 0, started_at: nil, finished_at: nil, @@ -160,7 +227,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d min_value: 11, max_value: 15, batch_size: 5, - status: 'failed', + status_name: :failed, attempts: 0, started_at: nil, finished_at: nil, @@ -177,7 +244,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end context 'when job is not failed' do - let!(:job) { create(:batched_background_migration_job, status: :succeeded) } + let!(:job) { create(:batched_background_migration_job, :succeeded) } it 'raises an exception' do expect { job.split_and_retry! }.to raise_error 'Only failed jobs can be split' @@ -185,7 +252,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end context 'when batch size is already 1' do - let!(:job) { create(:batched_background_migration_job, batch_size: 1, status: :failed) } + let!(:job) { create(:batched_background_migration_job, :failed, batch_size: 1) } it 'raises an exception' do expect { job.split_and_retry! }.to raise_error 'Job cannot be split further' @@ -204,7 +271,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d expect(job.batch_size).to eq(5) expect(job.attempts).to eq(0) - expect(job.status).to eq('failed') + expect(job.status_name).to eq(:failed) end end end diff --git a/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb new file mode 100644 index 00000000000..c42a0fc5e05 --- /dev/null +++ b/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJobTransitionLog, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:batched_job).with_foreign_key(:batched_background_migration_job_id) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:previous_status) } + it { is_expected.to validate_presence_of(:next_status) } + it { is_expected.to validate_presence_of(:batched_job) } + it { is_expected.to validate_length_of(:exception_class).is_at_most(100) } + it { is_expected.to validate_length_of(:exception_message).is_at_most(1000) } + it { is_expected.to define_enum_for(:previous_status).with_values(%i(pending running failed succeeded)).with_prefix } + it { is_expected.to define_enum_for(:next_status).with_values(%i(pending running failed succeeded)).with_prefix } + end +end diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb index 04c18a98ee6..bb2c6b9a3ae 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb @@ -96,13 +96,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do end let!(:previous_job) do - create(:batched_background_migration_job, + create(:batched_background_migration_job, :succeeded, batched_migration: migration, min_value: event1.id, max_value: event2.id, batch_size: 2, - sub_batch_size: 1, - status: :succeeded + sub_batch_size: 1 ) end @@ -144,7 +143,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when migration has failed jobs' do before do - previous_job.update!(status: :failed) + previous_job.failure! end it 'retries the failed job' do @@ -172,7 +171,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when migration has stuck jobs' do before do - previous_job.update!(status: :running, updated_at: 1.hour.ago - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) + previous_job.update!(status_event: 'run', updated_at: 1.hour.ago - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) end it 'retries the stuck job' do @@ -186,7 +185,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when migration has possible stuck jobs' do before do - previous_job.update!(status: :running, updated_at: 1.hour.from_now - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) + previous_job.update!(status_event: 'run', updated_at: 1.hour.from_now - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) end it 'keeps the migration active' do @@ -201,13 +200,13 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when the migration has batches to process and failed jobs' do before do migration.update!(max_value: event3.id) - previous_job.update!(status: :failed) + previous_job.failure! end it 'runs next batch then retries the failed job' do expect(migration_wrapper).to receive(:perform) do |job_record| expect(job_record).to eq(job_relation.last) - job_record.update!(status: :succeeded) + job_record.succeed! end expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(1) @@ -264,12 +263,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do it 'runs all jobs inline until finishing the migration' do expect(migration_wrapper).to receive(:perform) do |job_record| expect(job_record).to eq(job_relation.first) - job_record.update!(status: :succeeded) + job_record.succeed! end expect(migration_wrapper).to receive(:perform) do |job_record| expect(job_record).to eq(job_relation.last) - job_record.update!(status: :succeeded) + job_record.succeed! end expect { runner.run_entire_migration(migration) }.to change { job_relation.count }.by(2) @@ -330,9 +329,9 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do pause_ms: 0 } - create(:batched_background_migration_job, common_attributes.merge(status: :succeeded, min_value: 1, max_value: 2)) - create(:batched_background_migration_job, common_attributes.merge(status: :pending, min_value: 3, max_value: 4)) - create(:batched_background_migration_job, common_attributes.merge(status: :failed, min_value: 5, max_value: 6, attempts: 1)) + create(:batched_background_migration_job, :succeeded, common_attributes.merge(min_value: 1, max_value: 2)) + create(:batched_background_migration_job, :pending, common_attributes.merge(min_value: 3, max_value: 4)) + create(:batched_background_migration_job, :failed, common_attributes.merge(min_value: 5, max_value: 6, attempts: 1)) end it 'completes the migration' do @@ -359,7 +358,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when migration fails to complete' do it 'raises an error' do - batched_migration.batched_jobs.failed.update_all(attempts: Gitlab::Database::BackgroundMigration::BatchedJob::MAX_ATTEMPTS) + batched_migration.batched_jobs.with_status(:failed).update_all(attempts: Gitlab::Database::BackgroundMigration::BatchedJob::MAX_ATTEMPTS) expect do runner.finalize( diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb index 01d61a525e6..ea4ba4dd137 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m context 'when there are failed jobs' do let(:batched_migration) { create(:batched_background_migration, status: :active, total_tuple_count: 100) } - let!(:batched_job) { create(:batched_background_migration_job, batched_migration: batched_migration, status: :failed) } + let!(:batched_job) { create(:batched_background_migration_job, :failed, batched_migration: batched_migration) } it 'raises an exception' do expect { batched_migration.finished! }.to raise_error(ActiveRecord::RecordInvalid) @@ -37,7 +37,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m context 'when the jobs are completed' do let(:batched_migration) { create(:batched_background_migration, status: :active, total_tuple_count: 100) } - let!(:batched_job) { create(:batched_background_migration_job, batched_migration: batched_migration, status: :succeeded) } + let!(:batched_job) { create(:batched_background_migration_job, :succeeded, batched_migration: batched_migration) } it 'finishes the migration' do batched_migration.finished! @@ -64,7 +64,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m it 'returns the first active migration according to queue order' do expect(described_class.active_migration).to eq(migration2) - create(:batched_background_migration_job, batched_migration: migration1, batch_size: 1000, status: :succeeded) + create(:batched_background_migration_job, :succeeded, batched_migration: migration1, batch_size: 1000) end end @@ -84,10 +84,10 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m let!(:migration_without_jobs) { create(:batched_background_migration) } before do - create(:batched_background_migration_job, batched_migration: migration1, batch_size: 1000, status: :succeeded) - create(:batched_background_migration_job, batched_migration: migration1, batch_size: 200, status: :failed) - create(:batched_background_migration_job, batched_migration: migration2, batch_size: 500, status: :succeeded) - create(:batched_background_migration_job, batched_migration: migration2, batch_size: 200, status: :running) + create(:batched_background_migration_job, :succeeded, batched_migration: migration1, batch_size: 1000) + create(:batched_background_migration_job, :failed, batched_migration: migration1, batch_size: 200) + create(:batched_background_migration_job, :succeeded, batched_migration: migration2, batch_size: 500) + create(:batched_background_migration_job, :running, batched_migration: migration2, batch_size: 200) end it 'returns totals from successful jobs' do @@ -268,7 +268,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m subject(:retry_failed_jobs) { batched_migration.retry_failed_jobs! } context 'when there are failed migration jobs' do - let!(:batched_background_migration_job) { create(:batched_background_migration_job, batched_migration: batched_migration, batch_size: 10, min_value: 6, max_value: 15, status: :failed, attempts: 3) } + let!(:batched_background_migration_job) { create(:batched_background_migration_job, :failed, batched_migration: batched_migration, batch_size: 10, min_value: 6, max_value: 15, attempts: 3) } before do allow_next_instance_of(Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy) do |batch_class| @@ -312,9 +312,9 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m let(:batched_migration) { create(:batched_background_migration) } before do - create_list(:batched_background_migration_job, 5, status: :succeeded, batch_size: 1_000, batched_migration: batched_migration) - create_list(:batched_background_migration_job, 1, status: :running, batch_size: 1_000, batched_migration: batched_migration) - create_list(:batched_background_migration_job, 1, status: :failed, batch_size: 1_000, batched_migration: batched_migration) + create_list(:batched_background_migration_job, 5, :succeeded, batch_size: 1_000, batched_migration: batched_migration) + create_list(:batched_background_migration_job, 1, :running, batch_size: 1_000, batched_migration: batched_migration) + create_list(:batched_background_migration_job, 1, :failed, batch_size: 1_000, batched_migration: batched_migration) end it 'sums the batch_size of succeeded jobs' do @@ -347,7 +347,6 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m let_it_be(:common_attrs) do { - status: :succeeded, batched_migration: migration, finished_at: end_time } @@ -357,7 +356,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m subject { migration.smoothed_time_efficiency(number_of_jobs: 10) } it 'returns nil' do - create_list(:batched_background_migration_job, 9, **common_attrs) + create_list(:batched_background_migration_job, 9, :succeeded, **common_attrs) expect(subject).to be_nil end @@ -369,6 +368,8 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m subject { migration.smoothed_time_efficiency(number_of_jobs: number_of_jobs) } + let!(:jobs) { create_list(:batched_background_migration_job, number_of_jobs, :succeeded, **common_attrs.merge(batched_migration: migration)) } + before do expect(migration).to receive_message_chain(:batched_jobs, :successful_in_execution_order, :reverse_order, :limit, :with_preloads) .and_return(jobs) diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb index c1183a15e37..4f5536d8771 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb @@ -35,8 +35,6 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, ' expect(job_instance).to receive(:perform) expect(job_instance).to receive(:batch_metrics).and_return(test_metrics) - expect(job_record).to receive(:update!).with(hash_including(attempts: 1, status: :running)).and_call_original - freeze_time do subject @@ -51,11 +49,10 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, ' context 'when running a job that failed previously' do let!(:job_record) do - create(:batched_background_migration_job, + create(:batched_background_migration_job, :failed, batched_migration: active_migration, pause_ms: pause_ms, attempts: 1, - status: :failed, finished_at: 1.hour.ago, metrics: { 'my_metrics' => 'some_value' } ) @@ -67,10 +64,6 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, ' expect(job_instance).to receive(:perform) expect(job_instance).to receive(:batch_metrics).and_return(updated_metrics) - expect(job_record).to receive(:update!).with( - hash_including(attempts: 2, status: :running, finished_at: nil, metrics: {}) - ).and_call_original - freeze_time do subject @@ -201,4 +194,44 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, ' it_behaves_like 'an error is raised', RuntimeError.new('Something broke!') it_behaves_like 'an error is raised', SignalException.new('SIGTERM') end + + context 'when the batched background migration does not inherit from BaseJob' do + let(:migration_class) { Class.new } + + before do + stub_const('Gitlab::BackgroundMigration::Foo', migration_class) + end + + let(:connection) { double(:connection) } + let(:active_migration) { create(:batched_background_migration, :active, job_class_name: 'Foo') } + let!(:job_record) { create(:batched_background_migration_job, batched_migration: active_migration) } + + it 'does not pass any argument' do + expect(Gitlab::BackgroundMigration::Foo).to receive(:new).with(no_args).and_return(job_instance) + + expect(job_instance).to receive(:perform) + + described_class.new(connection: connection).perform(job_record) + end + end + + context 'when the batched background migration inherits from BaseJob' do + let(:connection) { double(:connection) } + let(:active_migration) { create(:batched_background_migration, :active, job_class_name: 'Foo') } + let!(:job_record) { create(:batched_background_migration_job, batched_migration: active_migration) } + + let(:migration_class) { Class.new(::Gitlab::BackgroundMigration::BaseJob) } + + before do + stub_const('Gitlab::BackgroundMigration::Foo', migration_class) + end + + it 'passes the correct connection' do + expect(Gitlab::BackgroundMigration::Foo).to receive(:new).with(connection: connection).and_return(job_instance) + + expect(job_instance).to receive(:perform) + + described_class.new(connection: connection).perform(job_record) + end + end end diff --git a/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb index 0844616ee1c..31486240bfa 100644 --- a/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb +++ b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb @@ -4,10 +4,11 @@ require 'spec_helper' RSpec.describe Gitlab::Database::DynamicModelHelpers do let(:including_class) { Class.new.include(described_class) } - let(:table_name) { 'projects' } + let(:table_name) { Project.table_name } + let(:connection) { Project.connection } describe '#define_batchable_model' do - subject { including_class.new.define_batchable_model(table_name) } + subject { including_class.new.define_batchable_model(table_name, connection: connection) } it 'is an ActiveRecord model' do expect(subject.ancestors).to include(ActiveRecord::Base) @@ -40,7 +41,7 @@ RSpec.describe Gitlab::Database::DynamicModelHelpers do it 'iterates table in batches' do each_batch_size = ->(&block) do - subject.each_batch(table_name, of: 1) do |batch| + subject.each_batch(table_name, connection: connection, of: 1) do |batch| block.call(batch.size) end end @@ -56,7 +57,7 @@ RSpec.describe Gitlab::Database::DynamicModelHelpers do end it 'raises an error' do - expect { subject.each_batch(table_name, of: 1) { |batch| batch.size } } + expect { subject.each_batch(table_name, connection: connection, of: 1) { |batch| batch.size } } .to raise_error(RuntimeError, /each_batch should not run inside a transaction/) end end @@ -74,7 +75,7 @@ RSpec.describe Gitlab::Database::DynamicModelHelpers do end it 'iterates table in batch ranges' do - expect { |b| subject.each_batch_range(table_name, of: 1, &b) } + expect { |b| subject.each_batch_range(table_name, connection: connection, of: 1, &b) } .to yield_successive_args( [first_project.id, first_project.id], [second_project.id, second_project.id] @@ -82,13 +83,13 @@ RSpec.describe Gitlab::Database::DynamicModelHelpers do end it 'yields only one batch if bigger than the table size' do - expect { |b| subject.each_batch_range(table_name, of: 2, &b) } + expect { |b| subject.each_batch_range(table_name, connection: connection, of: 2, &b) } .to yield_successive_args([first_project.id, second_project.id]) end it 'makes it possible to apply a scope' do each_batch_limited = ->(&b) do - subject.each_batch_range(table_name, scope: ->(table) { table.limit(1) }, of: 1, &b) + subject.each_batch_range(table_name, connection: connection, scope: ->(table) { table.limit(1) }, of: 1, &b) end expect { |b| each_batch_limited.call(&b) } @@ -102,7 +103,7 @@ RSpec.describe Gitlab::Database::DynamicModelHelpers do end it 'raises an error' do - expect { subject.each_batch_range(table_name, of: 1) { 1 } } + expect { subject.each_batch_range(table_name, connection: connection, of: 1) { 1 } } .to raise_error(RuntimeError, /each_batch should not run inside a transaction/) end end diff --git a/spec/lib/gitlab/database/each_database_spec.rb b/spec/lib/gitlab/database/each_database_spec.rb index 9327fc4ff78..d526b3bc1ac 100644 --- a/spec/lib/gitlab/database/each_database_spec.rb +++ b/spec/lib/gitlab/database/each_database_spec.rb @@ -4,45 +4,97 @@ require 'spec_helper' RSpec.describe Gitlab::Database::EachDatabase do describe '.each_database_connection' do - let(:expected_connections) do - Gitlab::Database.database_base_models.map { |name, model| [model.connection, name] } + before do + allow(Gitlab::Database).to receive(:database_base_models) + .and_return({ main: ActiveRecord::Base, ci: Ci::ApplicationRecord }.with_indifferent_access) end - it 'yields each connection after connecting SharedModel' do - expected_connections.each do |connection, _| - expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(connection).and_yield - end + it 'yields each connection after connecting SharedModel', :add_ci_connection do + expect(Gitlab::Database::SharedModel).to receive(:using_connection) + .with(ActiveRecord::Base.connection).ordered.and_yield - yielded_connections = [] + expect(Gitlab::Database::SharedModel).to receive(:using_connection) + .with(Ci::ApplicationRecord.connection).ordered.and_yield - described_class.each_database_connection do |connection, name| - yielded_connections << [connection, name] - end - - expect(yielded_connections).to match_array(expected_connections) + expect { |b| described_class.each_database_connection(&b) } + .to yield_successive_args( + [ActiveRecord::Base.connection, 'main'], + [Ci::ApplicationRecord.connection, 'ci'] + ) end end describe '.each_model_connection' do - let(:model1) { double(connection: double, table_name: 'table1') } - let(:model2) { double(connection: double, table_name: 'table2') } + context 'when the model inherits from SharedModel', :add_ci_connection do + let(:model1) { Class.new(Gitlab::Database::SharedModel) } + let(:model2) { Class.new(Gitlab::Database::SharedModel) } - before do - allow(model1.connection).to receive_message_chain('pool.db_config.name').and_return('name1') - allow(model2.connection).to receive_message_chain('pool.db_config.name').and_return('name2') + before do + allow(Gitlab::Database).to receive(:database_base_models) + .and_return({ main: ActiveRecord::Base, ci: Ci::ApplicationRecord }.with_indifferent_access) + end + + it 'yields each model with SharedModel connected to each database connection' do + expect_yielded_models([model1, model2], [ + { model: model1, connection: ActiveRecord::Base.connection, name: 'main' }, + { model: model1, connection: Ci::ApplicationRecord.connection, name: 'ci' }, + { model: model2, connection: ActiveRecord::Base.connection, name: 'main' }, + { model: model2, connection: Ci::ApplicationRecord.connection, name: 'ci' } + ]) + end + + context 'when the model limits connection names' do + before do + model1.limit_connection_names = %i[main] + model2.limit_connection_names = %i[ci] + end + + it 'only yields the model with SharedModel connected to the limited connections' do + expect_yielded_models([model1, model2], [ + { model: model1, connection: ActiveRecord::Base.connection, name: 'main' }, + { model: model2, connection: Ci::ApplicationRecord.connection, name: 'ci' } + ]) + end + end + end + + context 'when the model does not inherit from SharedModel' do + let(:main_model) { Class.new(ActiveRecord::Base) } + let(:ci_model) { Class.new(Ci::ApplicationRecord) } + + let(:main_connection) { double(:connection) } + let(:ci_connection) { double(:connection) } + + before do + allow(main_model).to receive(:connection).and_return(main_connection) + allow(ci_model).to receive(:connection).and_return(ci_connection) + + allow(main_connection).to receive_message_chain('pool.db_config.name').and_return('main') + allow(ci_connection).to receive_message_chain('pool.db_config.name').and_return('ci') + end + + it 'yields each model after connecting SharedModel' do + expect_yielded_models([main_model, ci_model], [ + { model: main_model, connection: main_connection, name: 'main' }, + { model: ci_model, connection: ci_connection, name: 'ci' } + ]) + end end - it 'yields each model after connecting SharedModel' do - expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(model1.connection).and_yield - expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(model2.connection).and_yield + def expect_yielded_models(models_to_iterate, expected_values) + times_yielded = 0 + + described_class.each_model_connection(models_to_iterate) do |model, name| + expected = expected_values[times_yielded] - yielded_models = [] + expect(model).to be(expected[:model]) + expect(model.connection).to be(expected[:connection]) + expect(name).to eq(expected[:name]) - described_class.each_model_connection([model1, model2]) do |model, name| - yielded_models << [model, name] + times_yielded += 1 end - expect(yielded_models).to match_array([[model1, 'name1'], [model2, 'name2']]) + expect(times_yielded).to eq(expected_values.size) end end end diff --git a/spec/lib/gitlab/database/gitlab_schema_spec.rb b/spec/lib/gitlab/database/gitlab_schema_spec.rb index 255efc99ff6..a5a67c2c918 100644 --- a/spec/lib/gitlab/database/gitlab_schema_spec.rb +++ b/spec/lib/gitlab/database/gitlab_schema_spec.rb @@ -44,6 +44,8 @@ RSpec.describe Gitlab::Database::GitlabSchema do 'my_schema.ci_builds' | :gitlab_ci 'information_schema.columns' | :gitlab_shared 'audit_events_part_5fc467ac26' | :gitlab_main + '_test_gitlab_main_table' | :gitlab_main + '_test_gitlab_ci_table' | :gitlab_ci '_test_my_table' | :gitlab_shared 'pg_attribute' | :gitlab_shared 'my_other_table' | :undefined_my_other_table diff --git a/spec/lib/gitlab/database/load_balancing/configuration_spec.rb b/spec/lib/gitlab/database/load_balancing/configuration_spec.rb index 796c14c1038..e87c9c20707 100644 --- a/spec/lib/gitlab/database/load_balancing/configuration_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/configuration_spec.rb @@ -2,11 +2,18 @@ require 'spec_helper' -RSpec.describe Gitlab::Database::LoadBalancing::Configuration do +RSpec.describe Gitlab::Database::LoadBalancing::Configuration, :request_store do let(:configuration_hash) { {} } let(:db_config) { ActiveRecord::DatabaseConfigurations::HashConfig.new('test', 'ci', configuration_hash) } let(:model) { double(:model, connection_db_config: db_config) } + before do + # It's confusing to think about these specs with this enabled by default so + # we make it disabled by default and just write the specific spec for when + # it's enabled + stub_feature_flags(force_no_sharing_primary_model: false) + end + describe '.for_model' do context 'when load balancing is not configured' do it 'uses the default settings' do @@ -233,11 +240,23 @@ RSpec.describe Gitlab::Database::LoadBalancing::Configuration do end context 'when GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci=main' do - it 'the primary connection uses main connection' do + before do stub_env('GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci', 'main') + end + it 'the primary connection uses main connection' do expect(config.primary_connection_specification_name).to eq('ActiveRecord::Base') end + + context 'when force_no_sharing_primary_model feature flag is enabled' do + before do + stub_feature_flags(force_no_sharing_primary_model: true) + end + + it 'the primary connection uses ci connection' do + expect(config.primary_connection_specification_name).to eq('Ci::ApplicationRecord') + end + end end context 'when GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci=unknown' do diff --git a/spec/lib/gitlab/database/load_balancing/setup_spec.rb b/spec/lib/gitlab/database/load_balancing/setup_spec.rb index 953d83d3b48..20519a759b2 100644 --- a/spec/lib/gitlab/database/load_balancing/setup_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/setup_spec.rb @@ -130,6 +130,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil, request_store_active: false, ff_use_model_load_balancing: nil, + ff_force_no_sharing_primary_model: false, expectations: { main: { read: 'main_replica', write: 'main' }, ci: { read: 'ci_replica', write: 'ci' } @@ -140,6 +141,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: 'main', request_store_active: false, ff_use_model_load_balancing: nil, + ff_force_no_sharing_primary_model: false, expectations: { main: { read: 'main_replica', write: 'main' }, ci: { read: 'ci_replica', write: 'main' } @@ -150,6 +152,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil, request_store_active: false, ff_use_model_load_balancing: nil, + ff_force_no_sharing_primary_model: false, expectations: { main: { read: 'main_replica', write: 'main' }, ci: { read: 'main_replica', write: 'main' } @@ -160,60 +163,77 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: 'main', request_store_active: false, ff_use_model_load_balancing: nil, + ff_force_no_sharing_primary_model: false, expectations: { main: { read: 'main_replica', write: 'main' }, ci: { read: 'main_replica', write: 'main' } } }, - "with FF disabled without RequestStore it uses main" => { + "with FF use_model_load_balancing disabled without RequestStore it uses main" => { env_GITLAB_USE_MODEL_LOAD_BALANCING: nil, env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil, request_store_active: false, ff_use_model_load_balancing: false, + ff_force_no_sharing_primary_model: false, expectations: { main: { read: 'main_replica', write: 'main' }, ci: { read: 'main_replica', write: 'main' } } }, - "with FF enabled without RequestStore sticking of FF does not work, so it fallbacks to use main" => { + "with FF use_model_load_balancing enabled without RequestStore sticking of FF does not work, so it fallbacks to use main" => { env_GITLAB_USE_MODEL_LOAD_BALANCING: nil, env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil, request_store_active: false, ff_use_model_load_balancing: true, + ff_force_no_sharing_primary_model: false, expectations: { main: { read: 'main_replica', write: 'main' }, ci: { read: 'main_replica', write: 'main' } } }, - "with FF disabled with RequestStore it uses main" => { + "with FF use_model_load_balancing disabled with RequestStore it uses main" => { env_GITLAB_USE_MODEL_LOAD_BALANCING: nil, env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil, request_store_active: true, ff_use_model_load_balancing: false, + ff_force_no_sharing_primary_model: false, expectations: { main: { read: 'main_replica', write: 'main' }, ci: { read: 'main_replica', write: 'main' } } }, - "with FF enabled with RequestStore it sticks FF and uses CI connection" => { + "with FF use_model_load_balancing enabled with RequestStore it sticks FF and uses CI connection" => { env_GITLAB_USE_MODEL_LOAD_BALANCING: nil, env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: nil, request_store_active: true, ff_use_model_load_balancing: true, + ff_force_no_sharing_primary_model: false, expectations: { main: { read: 'main_replica', write: 'main' }, ci: { read: 'ci_replica', write: 'ci' } } }, - "with re-use and FF enabled with RequestStore it sticks FF and uses CI connection for reads" => { + "with re-use and ff_use_model_load_balancing enabled and FF force_no_sharing_primary_model disabled with RequestStore it sticks FF and uses CI connection for reads" => { env_GITLAB_USE_MODEL_LOAD_BALANCING: nil, env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: 'main', request_store_active: true, ff_use_model_load_balancing: true, + ff_force_no_sharing_primary_model: false, expectations: { main: { read: 'main_replica', write: 'main' }, ci: { read: 'ci_replica', write: 'main' } } + }, + "with re-use and ff_use_model_load_balancing enabled and FF force_no_sharing_primary_model enabled with RequestStore it sticks FF and uses CI connection for reads" => { + env_GITLAB_USE_MODEL_LOAD_BALANCING: nil, + env_GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci: 'main', + request_store_active: true, + ff_use_model_load_balancing: true, + ff_force_no_sharing_primary_model: true, + expectations: { + main: { read: 'main_replica', write: 'main' }, + ci: { read: 'ci_replica', write: 'ci' } + } } } end @@ -243,6 +263,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Setup do around do |example| if request_store_active Gitlab::WithRequestStore.with_request_store do + stub_feature_flags(force_no_sharing_primary_model: ff_force_no_sharing_primary_model) RequestStore.clear! example.run diff --git a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb index 13f2d31bc32..ed11699e494 100644 --- a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb +++ b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb @@ -18,6 +18,45 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do )) end + context 'ensure keys are sorted' do + it 'does not have any keys that are out of order' do + parsed = YAML.parse_file(described_class.loose_foreign_keys_yaml_path) + mapping = parsed.children.first + table_names = mapping.children.select(&:scalar?).map(&:value) + expect(table_names).to eq(table_names.sort), "expected sorted table names in the YAML file" + end + end + + context 'ensure no duplicates are found' do + it 'does not have duplicate tables defined' do + # since we use hash to detect duplicate hash keys we need to parse YAML document + parsed = YAML.parse_file(described_class.loose_foreign_keys_yaml_path) + expect(parsed).to be_document + expect(parsed.children).to be_one, "YAML has a single document" + + # require hash + mapping = parsed.children.first + expect(mapping).to be_mapping, "YAML has a top-level hash" + + # find all scalars with names + table_names = mapping.children.select(&:scalar?).map(&:value) + expect(table_names).not_to be_empty, "YAML has a non-zero tables defined" + + # expect to not have duplicates + expect(table_names).to contain_exactly(*table_names.uniq) + end + + it 'does not have duplicate column definitions' do + # ignore other modifiers + all_definitions = definitions.map do |definition| + { from_table: definition.from_table, to_table: definition.to_table, column: definition.column } + end + + # expect to not have duplicates + expect(all_definitions).to contain_exactly(*all_definitions.uniq) + end + end + describe 'ensuring database integrity' do def base_models_for(table) parent_table_schema = Gitlab::Database::GitlabSchema.table_schema(table) diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 7e3de32b965..d71a4f81901 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -14,6 +14,54 @@ RSpec.describe Gitlab::Database::MigrationHelpers do allow(model).to receive(:puts) end + describe 'overridden dynamic model helpers' do + let(:test_table) { '__test_batching_table' } + + before do + model.connection.execute(<<~SQL) + CREATE TABLE #{test_table} ( + id integer NOT NULL PRIMARY KEY, + name text NOT NULL + ); + + INSERT INTO #{test_table} (id, name) + VALUES (1, 'bob'), (2, 'mary'), (3, 'amy'); + SQL + end + + describe '#define_batchable_model' do + it 'defines a batchable model with the migration connection' do + expect(model.define_batchable_model(test_table).count).to eq(3) + end + end + + describe '#each_batch' do + before do + allow(model).to receive(:transaction_open?).and_return(false) + end + + it 'calls each_batch with the migration connection' do + each_batch_name = ->(&block) do + model.each_batch(test_table, of: 2) do |batch| + block.call(batch.pluck(:name)) + end + end + + expect { |b| each_batch_name.call(&b) }.to yield_successive_args(%w[bob mary], %w[amy]) + end + end + + describe '#each_batch_range' do + before do + allow(model).to receive(:transaction_open?).and_return(false) + end + + it 'calls each_batch with the migration connection' do + expect { |b| model.each_batch_range(test_table, of: 2, &b) }.to yield_successive_args([1, 2], [3, 3]) + end + end + end + describe '#remove_timestamps' do it 'can remove the default timestamps' do Gitlab::Database::MigrationHelpers::DEFAULT_TIMESTAMP_COLUMNS.each do |column_name| @@ -442,6 +490,60 @@ RSpec.describe Gitlab::Database::MigrationHelpers do end end + describe '#remove_foreign_key_if_exists' do + context 'when the foreign key does not exist' do + before do + allow(model).to receive(:foreign_key_exists?).and_return(false) + end + + it 'does nothing' do + expect(model).not_to receive(:remove_foreign_key) + + model.remove_foreign_key_if_exists(:projects, :users, column: :user_id) + end + end + + context 'when the foreign key exists' do + before do + allow(model).to receive(:foreign_key_exists?).and_return(true) + end + + it 'removes the foreign key' do + expect(model).to receive(:remove_foreign_key).with(:projects, :users, { column: :user_id }) + + model.remove_foreign_key_if_exists(:projects, :users, column: :user_id) + end + + context 'when the target table is not given' do + it 'passes the options as the second parameter' do + expect(model).to receive(:remove_foreign_key).with(:projects, { column: :user_id }) + + model.remove_foreign_key_if_exists(:projects, column: :user_id) + end + end + + context 'when the reverse_lock_order option is given' do + it 'requests for lock before removing the foreign key' do + expect(model).to receive(:transaction_open?).and_return(true) + expect(model).to receive(:execute).with(/LOCK TABLE users, projects/) + expect(model).not_to receive(:remove_foreign_key).with(:projects, :users) + + model.remove_foreign_key_if_exists(:projects, :users, column: :user_id, reverse_lock_order: true) + end + + context 'when not inside a transaction' do + it 'does not lock' do + expect(model).to receive(:transaction_open?).and_return(false) + expect(model).not_to receive(:execute).with(/LOCK TABLE users, projects/) + expect(model).to receive(:remove_foreign_key).with(:projects, :users, { column: :user_id }) + + model.remove_foreign_key_if_exists(:projects, :users, column: :user_id, reverse_lock_order: true) + end + end + end + end + end + describe '#add_concurrent_foreign_key' do before do allow(model).to receive(:foreign_key_exists?).and_return(false) diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb index 0abb76b9f8a..96dc3a0fc28 100644 --- a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb @@ -299,7 +299,7 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do before do allow(Gitlab::BackgroundMigration).to receive(:coordinator_for_database) - .with('main').and_return(coordinator) + .with(tracking_database).and_return(coordinator) expect(coordinator).to receive(:migration_class_for) .with(job_class_name).at_least(:once) { job_class } @@ -403,7 +403,7 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do end context 'when a specific coordinator is given' do - let(:coordinator) { Gitlab::BackgroundMigration::JobCoordinator.for_tracking_database('main') } + let(:coordinator) { Gitlab::BackgroundMigration::JobCoordinator.for_tracking_database(tracking_database) } it 'uses that coordinator' do expect(coordinator).to receive(:perform_in).with(10.minutes, 'Class', 'Hello', 'World').and_call_original @@ -438,6 +438,16 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do it_behaves_like 'helpers that enqueue background migrations', BackgroundMigrationWorker, 'main' end + context 'when the migration is running against the ci database', if: Gitlab::Database.has_config?(:ci) do + around do |example| + Gitlab::Database::SharedModel.using_connection(::Ci::ApplicationRecord.connection) do + example.run + end + end + + it_behaves_like 'helpers that enqueue background migrations', BackgroundMigration::CiDatabaseWorker, 'ci' + end + describe '#delete_job_tracking' do let!(:job_class_name) { 'TestJob' } diff --git a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb index c45149d67bf..37efff165c7 100644 --- a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb @@ -59,6 +59,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d batch_max_value: 1000, batch_class_name: 'MyBatchClass', batch_size: 100, + max_batch_size: 10000, sub_batch_size: 10) end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1) @@ -71,6 +72,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d max_value: 1000, batch_class_name: 'MyBatchClass', batch_size: 100, + max_batch_size: 10000, sub_batch_size: 10, job_arguments: %w[], status: 'active', diff --git a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb index 902d8e13a63..fd8303c379c 100644 --- a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb +++ b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb @@ -66,55 +66,43 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do context 'on successful execution' do subject { described_class.new(result_dir: result_dir).observe(version: migration_version, name: migration_name, connection: connection) {} } - it 'records walltime' do + it 'records a valid observation', :aggregate_failures do expect(subject.walltime).not_to be_nil - end - - it 'records success' do expect(subject.success).to be_truthy - end - - it 'records the migration version' do expect(subject.version).to eq(migration_version) - end - - it 'records the migration name' do expect(subject.name).to eq(migration_name) end end context 'upon failure' do - subject { described_class.new(result_dir: result_dir).observe(version: migration_version, name: migration_name, connection: connection) { raise 'something went wrong' } } - - it 'raises the exception' do - expect { subject }.to raise_error(/something went wrong/) - end - - context 'retrieving observations' do - subject { instance.observations.first } - - before do - instance.observe(version: migration_version, name: migration_name, connection: connection) { raise 'something went wrong' } - rescue StandardError - # ignore - end + where(exception: ['something went wrong', SystemStackError, Interrupt]) + with_them do let(:instance) { described_class.new(result_dir: result_dir) } - it 'records walltime' do - expect(subject.walltime).not_to be_nil - end - - it 'records failure' do - expect(subject.success).to be_falsey - end + subject(:observe) { instance.observe(version: migration_version, name: migration_name, connection: connection) { raise exception } } - it 'records the migration version' do - expect(subject.version).to eq(migration_version) + it 'raises the exception' do + expect { observe }.to raise_error(exception) end - it 'records the migration name' do - expect(subject.name).to eq(migration_name) + context 'retrieving observations' do + subject { instance.observations.first } + + before do + observe + # rubocop:disable Lint/RescueException + rescue Exception + # rubocop:enable Lint/RescueException + # ignore (we expect this exception) + end + + it 'records a valid observation', :aggregate_failures do + expect(subject.walltime).not_to be_nil + expect(subject.success).to be_falsey + expect(subject.version).to eq(migration_version) + expect(subject.name).to eq(migration_name) + end end end end diff --git a/spec/lib/gitlab/database/migrations/lock_retry_mixin_spec.rb b/spec/lib/gitlab/database/migrations/lock_retry_mixin_spec.rb index 076fb9e8215..50ad77caaf1 100644 --- a/spec/lib/gitlab/database/migrations/lock_retry_mixin_spec.rb +++ b/spec/lib/gitlab/database/migrations/lock_retry_mixin_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::Database::Migrations::LockRetryMixin do describe Gitlab::Database::Migrations::LockRetryMixin::ActiveRecordMigrationProxyLockRetries do - let(:migration) { double } + let(:connection) { ActiveRecord::Base.connection } + let(:migration) { double(connection: connection) } let(:return_value) { double } let(:class_def) do Class.new do @@ -40,6 +41,18 @@ RSpec.describe Gitlab::Database::Migrations::LockRetryMixin do expect(result).to eq(return_value) end end + + describe '#migration_connection' do + subject { class_def.new(migration).migration_connection } + + it 'retrieves actual migration connection from #migration' do + expect(migration).to receive(:connection).and_return(return_value) + + result = subject + + expect(result).to eq(return_value) + end + end end describe Gitlab::Database::Migrations::LockRetryMixin::ActiveRecordMigratorLockRetries do @@ -96,7 +109,8 @@ RSpec.describe Gitlab::Database::Migrations::LockRetryMixin do context 'with transactions enabled and lock retries enabled' do let(:receiver) { double('receiver', use_transaction?: true)} - let(:migration) { double('migration', enable_lock_retries?: true) } + let(:migration) { double('migration', migration_connection: connection, enable_lock_retries?: true) } + let(:connection) { ActiveRecord::Base.connection } it 'calls super method' do p = proc { } diff --git a/spec/lib/gitlab/database/migrations/observers/query_details_spec.rb b/spec/lib/gitlab/database/migrations/observers/query_details_spec.rb index 5a19ae6581d..a757cac0a2a 100644 --- a/spec/lib/gitlab/database/migrations/observers/query_details_spec.rb +++ b/spec/lib/gitlab/database/migrations/observers/query_details_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Gitlab::Database::Migrations::Observers::QueryDetails do subject { described_class.new(observation, directory_path, connection) } let(:connection) { ActiveRecord::Migration.connection } - let(:observation) { Gitlab::Database::Migrations::Observation.new(migration_version, migration_name) } + let(:observation) { Gitlab::Database::Migrations::Observation.new(version: migration_version, name: migration_name) } let(:query) { "select date_trunc('day', $1::timestamptz) + $2 * (interval '1 hour')" } let(:query_binds) { [Time.current, 3] } let(:directory_path) { Dir.mktmpdir } diff --git a/spec/lib/gitlab/database/migrations/observers/query_log_spec.rb b/spec/lib/gitlab/database/migrations/observers/query_log_spec.rb index 7b01e39f5f1..eb66972e5ab 100644 --- a/spec/lib/gitlab/database/migrations/observers/query_log_spec.rb +++ b/spec/lib/gitlab/database/migrations/observers/query_log_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::Migrations::Observers::QueryLog do subject { described_class.new(observation, directory_path, connection) } - let(:observation) { Gitlab::Database::Migrations::Observation.new(migration_version, migration_name) } + let(:observation) { Gitlab::Database::Migrations::Observation.new(version: migration_version, name: migration_name) } let(:connection) { ActiveRecord::Migration.connection } let(:query) { 'select 1' } let(:directory_path) { Dir.mktmpdir } diff --git a/spec/lib/gitlab/database/migrations/observers/transaction_duration_spec.rb b/spec/lib/gitlab/database/migrations/observers/transaction_duration_spec.rb index b26bb8fbe41..f433e25b2ba 100644 --- a/spec/lib/gitlab/database/migrations/observers/transaction_duration_spec.rb +++ b/spec/lib/gitlab/database/migrations/observers/transaction_duration_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Gitlab::Database::Migrations::Observers::TransactionDuration do subject(:transaction_duration_observer) { described_class.new(observation, directory_path, connection) } let(:connection) { ActiveRecord::Migration.connection } - let(:observation) { Gitlab::Database::Migrations::Observation.new(migration_version, migration_name) } + let(:observation) { Gitlab::Database::Migrations::Observation.new(version: migration_version, name: migration_name) } let(:directory_path) { Dir.mktmpdir } let(:log_file) { "#{directory_path}/#{migration_version}_#{migration_name}-transaction-duration.json" } let(:transaction_duration) { Gitlab::Json.parse(File.read(log_file)) } diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb index e5a8143fcc3..f94a40c93e1 100644 --- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb +++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb @@ -3,59 +3,12 @@ require 'spec_helper' RSpec.describe 'cross-database foreign keys' do - # TODO: We are trying to empty out this list in - # https://gitlab.com/groups/gitlab-org/-/epics/7249 . Once we are done we can - # keep this test and assert that there are no cross-db foreign keys. We - # should not be adding anything to this list but should instead only add new - # loose foreign keys - # https://docs.gitlab.com/ee/development/database/loose_foreign_keys.html . + # Since we don't expect to have any cross-database foreign keys + # this is empty. If we will have an entry like + # `ci_daily_build_group_report_results.project_id` + # should be added. let(:allowed_cross_database_foreign_keys) do - %w( - ci_build_report_results.project_id - ci_builds.project_id - ci_builds_metadata.project_id - ci_daily_build_group_report_results.group_id - ci_daily_build_group_report_results.project_id - ci_freeze_periods.project_id - ci_job_artifacts.project_id - ci_job_token_project_scope_links.added_by_id - ci_job_token_project_scope_links.source_project_id - ci_job_token_project_scope_links.target_project_id - ci_pending_builds.namespace_id - ci_pending_builds.project_id - ci_pipeline_schedules.owner_id - ci_pipeline_schedules.project_id - ci_pipelines.merge_request_id - ci_pipelines.project_id - ci_project_monthly_usages.project_id - ci_refs.project_id - ci_resource_groups.project_id - ci_runner_namespaces.namespace_id - ci_runner_projects.project_id - ci_running_builds.project_id - ci_sources_pipelines.project_id - ci_sources_pipelines.source_project_id - ci_sources_projects.source_project_id - ci_stages.project_id - ci_subscriptions_projects.downstream_project_id - ci_subscriptions_projects.upstream_project_id - ci_triggers.owner_id - ci_triggers.project_id - ci_unit_tests.project_id - ci_variables.project_id - dast_profiles_pipelines.ci_pipeline_id - dast_scanner_profiles_builds.ci_build_id - dast_site_profiles_builds.ci_build_id - dast_site_profiles_pipelines.ci_pipeline_id - external_pull_requests.project_id - merge_requests.head_pipeline_id - merge_trains.pipeline_id - requirements_management_test_reports.build_id - security_scans.build_id - vulnerability_feedback.pipeline_id - vulnerability_occurrence_pipelines.pipeline_id - vulnerability_statistics.latest_pipeline_id - ).freeze + %w[].freeze end def foreign_keys_for(table_name) diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb index c41b4eeea10..22a70dc7df0 100644 --- a/spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb +++ b/spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb @@ -14,23 +14,41 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio Gitlab::Database::QueryAnalyzer.instance.within { example.run } end - shared_examples 'successful examples' do + describe 'context and suppress key names' do + describe '.context_key' do + it 'contains class name' do + expect(described_class.context_key) + .to eq 'analyzer_prevent_cross_database_modification_context'.to_sym + end + end + + describe '.suppress_key' do + it 'contains class name' do + expect(described_class.suppress_key) + .to eq 'analyzer_prevent_cross_database_modification_suppressed'.to_sym + end + end + end + + shared_examples 'successful examples' do |model:| + let(:model) { model } + context 'outside transaction' do it { expect { run_queries }.not_to raise_error } end - context 'within transaction' do + context "within #{model} transaction" do it do - Project.transaction do + model.transaction do expect { run_queries }.not_to raise_error end end end - context 'within nested transaction' do + context "within nested #{model} transaction" do it do - Project.transaction(requires_new: true) do - Project.transaction(requires_new: true) do + model.transaction(requires_new: true) do + model.transaction(requires_new: true) do expect { run_queries }.not_to raise_error end end @@ -38,13 +56,26 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio end end + shared_examples 'cross-database modification errors' do |model:| + let(:model) { model } + + context "within #{model} transaction" do + it 'raises error' do + model.transaction do + expect { run_queries }.to raise_error /Cross-database data modification/ + end + end + end + end + context 'when CI and other tables are read in a transaction' do def run_queries pipeline.reload project.reload end - include_examples 'successful examples' + include_examples 'successful examples', model: Project + include_examples 'successful examples', model: Ci::Pipeline end context 'when only CI data is modified' do @@ -53,7 +84,9 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio project.reload end - include_examples 'successful examples' + include_examples 'successful examples', model: Ci::Pipeline + + include_examples 'cross-database modification errors', model: Project end context 'when other data is modified' do @@ -62,7 +95,9 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio project.touch end - include_examples 'successful examples' + include_examples 'successful examples', model: Project + + include_examples 'cross-database modification errors', model: Ci::Pipeline end context 'when both CI and other data is modified' do @@ -144,7 +179,9 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio project.save! end - include_examples 'successful examples' + include_examples 'successful examples', model: Ci::Pipeline + + include_examples 'cross-database modification errors', model: Project end describe '.allow_cross_database_modification_within_transaction' do diff --git a/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb b/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb index 0282a7af0df..6c32fb3ca17 100644 --- a/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb +++ b/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb @@ -5,7 +5,8 @@ require 'spec_helper' RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do let(:env) { {} } let(:logger) { Gitlab::Database::WithLockRetries::NULL_LOGGER } - let(:subject) { described_class.new(env: env, logger: logger, timing_configuration: timing_configuration) } + let(:subject) { described_class.new(connection: connection, env: env, logger: logger, timing_configuration: timing_configuration) } + let(:connection) { ActiveRecord::Base.retrieve_connection } let(:timing_configuration) do [ @@ -67,7 +68,7 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do WHERE t.relkind = 'r' AND l.mode = 'ExclusiveLock' AND t.relname = '#{Project.table_name}' """ - expect(ActiveRecord::Base.connection.execute(check_exclusive_lock_query).to_a).to be_present + expect(connection.execute(check_exclusive_lock_query).to_a).to be_present end end @@ -96,8 +97,8 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do lock_fiber.resume end - ActiveRecord::Base.transaction do - ActiveRecord::Base.connection.execute("LOCK TABLE #{Project.table_name} in exclusive mode") + connection.transaction do + connection.execute("LOCK TABLE #{Project.table_name} in exclusive mode") lock_acquired = true end end @@ -115,7 +116,7 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do context 'setting the idle transaction timeout' do context 'when there is no outer transaction: disable_ddl_transaction! is set in the migration' do it 'does not disable the idle transaction timeout' do - allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) + allow(connection).to receive(:transaction_open?).and_return(false) allow(subject).to receive(:run_block_with_lock_timeout).once.and_raise(ActiveRecord::LockWaitTimeout) allow(subject).to receive(:run_block_with_lock_timeout).once @@ -127,7 +128,7 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do context 'when there is outer transaction: disable_ddl_transaction! is not set in the migration' do it 'disables the idle transaction timeout so the code can sleep and retry' do - allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(true) + allow(connection).to receive(:transaction_open?).and_return(true) n = 0 allow(subject).to receive(:run_block_with_lock_timeout).twice do @@ -184,8 +185,8 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do subject.run(raise_on_exhaustion: true) do lock_attempts += 1 - ActiveRecord::Base.transaction do - ActiveRecord::Base.connection.execute("LOCK TABLE #{Project.table_name} in exclusive mode") + connection.transaction do + connection.execute("LOCK TABLE #{Project.table_name} in exclusive mode") lock_acquired = true end end @@ -199,11 +200,11 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do context 'when statement timeout is reached' do it 'raises StatementInvalid error' do lock_acquired = false - ActiveRecord::Base.connection.execute("SET statement_timeout='100ms'") + connection.execute("SET statement_timeout='100ms'") expect do subject.run do - ActiveRecord::Base.connection.execute("SELECT 1 FROM pg_sleep(0.11)") # 110ms + connection.execute("SELECT 1 FROM pg_sleep(0.11)") # 110ms lock_acquired = true end end.to raise_error(ActiveRecord::StatementInvalid) @@ -216,11 +217,11 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do context 'restore local database variables' do it do - expect { subject.run {} }.not_to change { ActiveRecord::Base.connection.execute("SHOW lock_timeout").to_a } + expect { subject.run {} }.not_to change { connection.execute("SHOW lock_timeout").to_a } end it do - expect { subject.run {} }.not_to change { ActiveRecord::Base.connection.execute("SHOW idle_in_transaction_session_timeout").to_a } + expect { subject.run {} }.not_to change { connection.execute("SHOW idle_in_transaction_session_timeout").to_a } end end @@ -228,8 +229,8 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do let(:timing_configuration) { [[0.015.seconds, 0.025.seconds], [0.015.seconds, 0.025.seconds]] } # 15ms, 25ms it 'executes `SET lock_timeout` using the configured timeout value in milliseconds' do - expect(ActiveRecord::Base.connection).to receive(:execute).with('RESET idle_in_transaction_session_timeout; RESET lock_timeout').and_call_original - expect(ActiveRecord::Base.connection).to receive(:execute).with("SET lock_timeout TO '15ms'").and_call_original + expect(connection).to receive(:execute).with('RESET idle_in_transaction_session_timeout; RESET lock_timeout').and_call_original + expect(connection).to receive(:execute).with("SET lock_timeout TO '15ms'").and_call_original subject.run { } end diff --git a/spec/lib/gitlab/database/with_lock_retries_spec.rb b/spec/lib/gitlab/database/with_lock_retries_spec.rb index c2c818aa106..6b35ccafabc 100644 --- a/spec/lib/gitlab/database/with_lock_retries_spec.rb +++ b/spec/lib/gitlab/database/with_lock_retries_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::WithLockRetries do let(:env) { {} } let(:logger) { Gitlab::Database::WithLockRetries::NULL_LOGGER } - let(:subject) { described_class.new(env: env, logger: logger, allow_savepoints: allow_savepoints, timing_configuration: timing_configuration) } + let(:subject) { described_class.new(connection: connection, env: env, logger: logger, allow_savepoints: allow_savepoints, timing_configuration: timing_configuration) } let(:allow_savepoints) { true } let(:connection) { ActiveRecord::Base.retrieve_connection } diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb index f5ea660ee1e..6601b6658d5 100644 --- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb +++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - application_setting.update(allow_local_requests_from_web_hooks_and_services: true) + application_setting.update!(allow_local_requests_from_web_hooks_and_services: true) end shared_examples 'has prometheus integration' do |server_address| @@ -181,7 +181,7 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService let(:existing_project) { create(:project, namespace: existing_group) } before do - application_setting.update(instance_administrators_group_id: existing_group.id, + application_setting.update!(instance_administrators_group_id: existing_group.id, self_monitoring_project_id: existing_project.id) end @@ -195,7 +195,7 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService context 'when local requests from hooks and integrations are not allowed' do before do - application_setting.update(allow_local_requests_from_web_hooks_and_services: false) + application_setting.update!(allow_local_requests_from_web_hooks_and_services: false) end it_behaves_like 'has prometheus integration', 'http://localhost:9090' diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 5ec7c338a2a..b3b7c81e9e7 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -104,6 +104,34 @@ RSpec.describe Gitlab::Database do end end + describe '.check_for_non_superuser' do + subject { described_class.check_for_non_superuser } + + let(:non_superuser) { Gitlab::Database::PgUser.new(usename: 'foo', usesuper: false ) } + let(:superuser) { Gitlab::Database::PgUser.new(usename: 'bar', usesuper: true) } + + it 'prints user details if not superuser' do + allow(Gitlab::Database::PgUser).to receive(:find_by).with('usename = CURRENT_USER').and_return(non_superuser) + + expect(Gitlab::AppLogger).to receive(:info).with("Account details: User: \"foo\", UseSuper: (false)") + + subject + end + + it 'raises an exception if superuser' do + allow(Gitlab::Database::PgUser).to receive(:find_by).with('usename = CURRENT_USER').and_return(superuser) + + expect(Gitlab::AppLogger).to receive(:info).with("Account details: User: \"bar\", UseSuper: (true)") + expect { subject }.to raise_error('Error: detected superuser') + end + + it 'catches exception if find_by fails' do + allow(Gitlab::Database::PgUser).to receive(:find_by).with('usename = CURRENT_USER').and_raise(ActiveRecord::StatementInvalid) + + expect { subject }.to raise_error('User CURRENT_USER not found') + end + end + describe '.check_postgres_version_and_print_warning' do let(:reflect) { instance_spy(Gitlab::Database::Reflection) } diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 45a49a36fe2..7c1a8f4c3c8 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Gitlab::Diff::File do def create_file(file_name, content) Files::CreateService.new( project, - project.owner, + project.first_owner, commit_message: 'Update', start_branch: branch_name, branch_name: branch_name, @@ -27,7 +27,7 @@ RSpec.describe Gitlab::Diff::File do def update_file(file_name, content) Files::UpdateService.new( project, - project.owner, + project.first_owner, commit_message: 'Update', start_branch: branch_name, branch_name: branch_name, @@ -41,7 +41,7 @@ RSpec.describe Gitlab::Diff::File do def delete_file(file_name) Files::DeleteService.new( project, - project.owner, + project.first_owner, commit_message: 'Update', start_branch: branch_name, branch_name: branch_name, diff --git a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb index 7dceb64b776..1414056ad6a 100644 --- a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::Diff::PositionTracer::ImageStrategy do include PositionTracerHelpers let(:project) { create(:project, :repository) } - let(:current_user) { project.owner } + let(:current_user) { project.first_owner } let(:file_name) { 'test-file' } let(:new_file_name) { "#{file_name}-new" } let(:second_file_name) { "#{file_name}-2" } diff --git a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb index c46f476899e..ea56a87dec2 100644 --- a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb @@ -55,7 +55,7 @@ RSpec.describe Gitlab::Diff::PositionTracer::LineStrategy, :clean_gitlab_redis_c include PositionTracerHelpers let(:project) { create(:project, :repository) } - let(:current_user) { project.owner } + let(:current_user) { project.first_owner } let(:repository) { project.repository } let(:file_name) { "test-file" } let(:new_file_name) { "#{file_name}-new" } diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index fc649812b0a..9b0ea892f91 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -52,7 +52,7 @@ RSpec.describe Gitlab::Diff::PositionTracer do describe 'diffs methods' do let(:project) { create(:project, :repository) } - let(:current_user) { project.owner } + let(:current_user) { project.first_owner } let(:old_diff_refs) do diff_refs( diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index c0ac40e3249..59b87c5d8e7 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do include_context :email_shared_context - let_it_be(:user) { create(:user) } + let_it_be(:user) { create(:user, email: 'jake@adventuretime.ooo') } let_it_be(:project) { create(:project, :public, :repository) } let(:noteable) { note.noteable } @@ -39,6 +39,43 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do end end + context 'when the incoming email is from a different email address' do + before do + SentNotification.find_by(reply_key: mail_key).update!(recipient: original_recipient) + end + + context 'when the issue is not a Service Desk issue' do + let(:original_recipient) { create(:user, email: 'john@somethingelse.com') } + + context 'with only one email address' do + it 'raises a UserNotFoundError' do + expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError) + end + end + + context 'with a secondary verified email address' do + let(:verified_email) { 'alan@adventuretime.ooo'} + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub('jake@adventuretime.ooo', verified_email) } + + before do + create(:email, :confirmed, user: original_recipient, email: verified_email) + end + + it 'does not raise a UserNotFoundError' do + expect { receiver.execute }.not_to raise_error(Gitlab::Email::UserNotFoundError) + end + end + end + + context 'when the issue is a Service Desk issue' do + let(:original_recipient) { User.support_bot } + + it 'does not raise a UserNotFoundError' do + expect { receiver.execute }.not_to raise_error(Gitlab::Email::UserNotFoundError) + end + end + end + context 'when no sent notification for the mail key could be found' do let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') } diff --git a/spec/lib/gitlab/endpoint_attributes_spec.rb b/spec/lib/gitlab/endpoint_attributes_spec.rb index 4d4cfed57fa..53f5b302f05 100644 --- a/spec/lib/gitlab/endpoint_attributes_spec.rb +++ b/spec/lib/gitlab/endpoint_attributes_spec.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true require 'fast_spec_helper' -require_relative "../../support/matchers/be_request_urgency" -require_relative "../../../lib/gitlab/endpoint_attributes" +require_relative '../../support/matchers/be_request_urgency' +require_relative '../../../lib/gitlab/endpoint_attributes/config' +require_relative '../../../lib/gitlab/endpoint_attributes' RSpec.describe Gitlab::EndpointAttributes do let(:base_controller) do diff --git a/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb b/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb index 0e72dd7ec5e..38745fe0cde 100644 --- a/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb +++ b/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'rspec-parameterized' +require 'spec_helper' RSpec.describe Gitlab::ErrorTracking::ContextPayloadGenerator do subject(:generator) { described_class.new } diff --git a/spec/lib/gitlab/error_tracking/log_formatter_spec.rb b/spec/lib/gitlab/error_tracking/log_formatter_spec.rb index 188ccd000a1..15d201401f4 100644 --- a/spec/lib/gitlab/error_tracking/log_formatter_spec.rb +++ b/spec/lib/gitlab/error_tracking/log_formatter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::ErrorTracking::LogFormatter do let(:exception) { StandardError.new('boom') } diff --git a/spec/lib/gitlab/event_store/store_spec.rb b/spec/lib/gitlab/event_store/store_spec.rb index 711e1d5b4d5..94e8f0ff2ff 100644 --- a/spec/lib/gitlab/event_store/store_spec.rb +++ b/spec/lib/gitlab/event_store/store_spec.rb @@ -10,7 +10,6 @@ RSpec.describe Gitlab::EventStore::Store do let(:worker) do stub_const('EventSubscriber', Class.new).tap do |klass| klass.class_eval do - include ApplicationWorker include Gitlab::EventStore::Subscriber def handle_event(event) @@ -23,7 +22,6 @@ RSpec.describe Gitlab::EventStore::Store do let(:another_worker) do stub_const('AnotherEventSubscriber', Class.new).tap do |klass| klass.class_eval do - include ApplicationWorker include Gitlab::EventStore::Subscriber end end @@ -32,7 +30,6 @@ RSpec.describe Gitlab::EventStore::Store do let(:unrelated_worker) do stub_const('UnrelatedEventSubscriber', Class.new).tap do |klass| klass.class_eval do - include ApplicationWorker include Gitlab::EventStore::Subscriber end end @@ -224,6 +221,26 @@ RSpec.describe Gitlab::EventStore::Store do store.publish(event) end end + + context 'when the event does not have any subscribers' do + let(:store) do + described_class.new do |s| + s.subscribe unrelated_worker, to: another_event_klass + end + end + + let(:event) { event_klass.new(data: data) } + + it 'returns successfully' do + expect { store.publish(event) }.not_to raise_error + end + + it 'does not dispatch the event to another subscription' do + expect(unrelated_worker).not_to receive(:perform_async) + + store.publish(event) + end + end end describe 'subscriber' do @@ -233,6 +250,10 @@ RSpec.describe Gitlab::EventStore::Store do subject { worker_instance.perform(event_name, data) } + it 'is a Sidekiq worker' do + expect(worker_instance).to be_a(ApplicationWorker) + end + it 'handles the event' do expect(worker_instance).to receive(:handle_event).with(instance_of(event.class)) diff --git a/spec/lib/gitlab/experiment/rollout/feature_spec.rb b/spec/lib/gitlab/experiment/rollout/feature_spec.rb new file mode 100644 index 00000000000..d73757be79b --- /dev/null +++ b/spec/lib/gitlab/experiment/rollout/feature_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Experiment::Rollout::Feature, :experiment do + subject { described_class.new.for(subject_experiment) } + + let(:subject_experiment) { experiment('namespaced/stub') } + + describe "#enabled?" do + before do + allow(Feature::Definition).to receive(:get).and_return('_instance_') + allow(Gitlab).to receive(:dev_env_or_com?).and_return(true) + allow(Feature).to receive(:get).and_return(double(state: :on)) + end + + it "is enabled when all criteria are met" do + expect(subject).to be_enabled + end + + it "isn't enabled if the feature definition doesn't exist" do + expect(Feature::Definition).to receive(:get).with('namespaced_stub').and_return(nil) + + expect(subject).not_to be_enabled + end + + it "isn't enabled if we're not in dev or dotcom environments" do + expect(Gitlab).to receive(:dev_env_or_com?).and_return(false) + + expect(subject).not_to be_enabled + end + + it "isn't enabled if the feature flag state is :off" do + expect(Feature).to receive(:get).with('namespaced_stub').and_return(double(state: :off)) + + expect(subject).not_to be_enabled + end + end + + describe "#execute_assignment" do + before do + allow(Feature).to receive(:enabled?).with('namespaced_stub', any_args).and_return(true) + end + + it "uses the default value as specified in the yaml" do + expect(Feature).to receive(:enabled?).with( + 'namespaced_stub', + subject, + type: :experiment, + default_enabled: :yaml + ).and_return(false) + + expect(subject.execute_assignment).to be_nil + end + + it "returns an assigned name" do + allow(subject).to receive(:behavior_names).and_return([:variant1, :variant2]) + + expect(subject.execute_assignment).to eq(:variant2) + end + end + + describe "#flipper_id" do + it "returns the expected flipper id if the experiment doesn't provide one" do + subject.instance_variable_set(:@experiment, double(id: '__id__')) + expect(subject.flipper_id).to eq('Experiment;__id__') + end + + it "lets the experiment provide a flipper id so it can override the default" do + allow(subject_experiment).to receive(:flipper_id).and_return('_my_overridden_id_') + + expect(subject.flipper_id).to eq('_my_overridden_id_') + end + end +end diff --git a/spec/lib/gitlab/feature_categories_spec.rb b/spec/lib/gitlab/feature_categories_spec.rb index daced154a69..477da900d0a 100644 --- a/spec/lib/gitlab/feature_categories_spec.rb +++ b/spec/lib/gitlab/feature_categories_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::FeatureCategories do let(:fake_categories) { %w(foo bar) } diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index f1b6a59abf9..ae6ca728573 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -2252,44 +2252,6 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do end end - describe '#clean_stale_repository_files' do - let(:worktree_id) { 'rebase-1' } - let(:gitlab_worktree_path) { File.join(repository_path, 'gitlab-worktree', worktree_id) } - let(:admin_dir) { File.join(repository_path, 'worktrees') } - - it 'cleans up the files' do - create_worktree = %W[git -C #{repository_path} worktree add --detach #{gitlab_worktree_path} master] - raise 'preparation failed' unless system(*create_worktree, err: '/dev/null') - - FileUtils.touch(gitlab_worktree_path, mtime: Time.now - 8.hours) - # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object, - # but the HEAD must be 40 characters long or git will ignore it. - File.write(File.join(admin_dir, worktree_id, 'HEAD'), Gitlab::Git::BLANK_SHA) - - expect(rev_list_all).to be(false) - repository.clean_stale_repository_files - - expect(rev_list_all).to be(true) - expect(File.exist?(gitlab_worktree_path)).to be_falsey - end - - def rev_list_all - system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null') - end - - it 'increments a counter upon an error' do - expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError) - - counter = double(:counter) - - expect(counter).to receive(:increment) - expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total, - 'Number of failed repository cleanup events').and_return(counter) - - repository.clean_stale_repository_files - end - end - describe '#squash' do let(:branch_name) { 'fix' } let(:start_sha) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' } diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb index eb7deb08063..ee0c0e2708e 100644 --- a/spec/lib/gitlab/git/wiki_spec.rb +++ b/spec/lib/gitlab/git/wiki_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::Git::Wiki do using RSpec::Parameterized::TableSyntax let(:project) { create(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:project_wiki) { ProjectWiki.new(project, user) } subject(:wiki) { project_wiki.wiki } diff --git a/spec/lib/gitlab/git_access_design_spec.rb b/spec/lib/gitlab/git_access_design_spec.rb index 9fd1f2dcb0c..c90d9802300 100644 --- a/spec/lib/gitlab/git_access_design_spec.rb +++ b/spec/lib/gitlab/git_access_design_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Gitlab::GitAccessDesign do include DesignManagementTestHelpers let_it_be(:project) { create(:project) } - let_it_be(:user) { project.owner } + let_it_be(:user) { project.first_owner } let(:protocol) { 'web' } let(:actor) { user } diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 4bf7994f4dd..d6ef1836ad9 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1008,11 +1008,6 @@ RSpec.describe Gitlab::GitAccess do end end - it 'cleans up the files' do - expect(project.repository).to receive(:clean_stale_repository_files).and_call_original - expect { push_access_check }.not_to raise_error - end - it 'avoids N+1 queries', :request_store do # Run this once to establish a baseline. Cached queries should get # cached, so that when we introduce another change we shouldn't see diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index 27e7d446770..f0115aa6b2b 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -2,6 +2,9 @@ require 'spec_helper' +require 'google/rpc/status_pb' +require 'google/protobuf/well_known_types' + RSpec.describe Gitlab::GitalyClient::OperationService do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :repository) } @@ -185,11 +188,16 @@ RSpec.describe Gitlab::GitalyClient::OperationService do context 'with an exception with the UserMergeBranchError' do let(:permission_error) do - GRPC::PermissionDenied.new( + new_detailed_error( + GRPC::Core::StatusCodes::PERMISSION_DENIED, "GitLab: You are not allowed to push code to this project.", - { "grpc-status-details-bin" => - "\b\a\x129GitLab: You are not allowed to push code to this project.\x1A\xDE\x01\n/type.googleapis.com/gitaly.UserMergeBranchError\x12\xAA\x01\n\xA7\x01\n1You are not allowed to push code to this project.\x12\x03web\x1A\auser-15\"df15b32277d2c55c6c595845a87109b09c913c556 5d6e0f935ad9240655f64e883cd98fad6f9a17ee refs/heads/master\n" } - ) + Gitaly::UserMergeBranchError.new( + access_check: Gitaly::AccessCheckError.new( + error_message: "You are not allowed to push code to this project.", + protocol: "web", + user_id: "user-15", + changes: "df15b32277d2c55c6c595845a87109b09c913c556 5d6e0f935ad9240655f64e883cd98fad6f9a17ee refs/heads/master\n" + ))) end it 'raises PreRecieveError with the error message' do @@ -217,6 +225,27 @@ RSpec.describe Gitlab::GitalyClient::OperationService do expect { subject }.to raise_error(GRPC::PermissionDenied) end end + + context 'with ReferenceUpdateError' do + let(:reference_update_error) do + new_detailed_error(GRPC::Core::StatusCodes::FAILED_PRECONDITION, + "some ignored error message", + Gitaly::UserMergeBranchError.new( + reference_update: Gitaly::ReferenceUpdateError.new( + reference_name: "refs/heads/something", + old_oid: "1234", + new_oid: "6789" + ))) + end + + it 'returns nil' do + expect_any_instance_of(Gitaly::OperationService::Stub) + .to receive(:user_merge_branch).with(kind_of(Enumerator), kind_of(Hash)) + .and_raise(reference_update_error) + + expect(subject).to be_nil + end + end end describe '#user_ff_branch' do @@ -478,4 +507,14 @@ RSpec.describe Gitlab::GitalyClient::OperationService do end end end + + def new_detailed_error(error_code, error_message, details) + status_error = Google::Rpc::Status.new( + code: error_code, + message: error_message, + details: [Google::Protobuf::Any.pack(details)] + ) + + GRPC::BadStatus.new(error_code, error_message, { "grpc-status-details-bin" => Google::Rpc::Status.encode(status_error) }) + end end diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index e5502a883b5..353726b56f6 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -21,16 +21,6 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do end end - describe '#cleanup' do - it 'sends a cleanup message' do - expect_any_instance_of(Gitaly::RepositoryService::Stub) - .to receive(:cleanup) - .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) - - client.cleanup - end - end - describe '#garbage_collect' do it 'sends a garbage_collect message' do expect_any_instance_of(Gitaly::RepositoryService::Stub) diff --git a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb index a0e78186caa..c8e744ab262 100644 --- a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb @@ -119,123 +119,80 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail .and_return(discussion_id) end - context 'when github_importer_use_diff_note_with_suggestions is disabled' do - before do - stub_feature_flags(github_importer_use_diff_note_with_suggestions: false) + it_behaves_like 'diff notes without suggestion' + + context 'when the note has suggestions' do + let(:note_body) do + <<~EOB + Suggestion: + ```suggestion + what do you think to do it like this + ``` + EOB end - it_behaves_like 'diff notes without suggestion' + before do + stub_user_finder(user.id, true) + end - context 'when the note has suggestions' do - let(:note_body) do - <<~EOB + it 'imports the note as diff note' do + expect { subject.execute } + .to change(DiffNote, :count) + .by(1) + + note = project.notes.diff_notes.take + expect(note).to be_valid + expect(note.noteable_type).to eq('MergeRequest') + expect(note.noteable_id).to eq(merge_request.id) + expect(note.project_id).to eq(project.id) + expect(note.author_id).to eq(user.id) + expect(note.system).to eq(false) + expect(note.discussion_id).to eq(discussion_id) + expect(note.commit_id).to eq('original123abc') + expect(note.line_code).to eq(note_representation.line_code) + expect(note.type).to eq('DiffNote') + expect(note.created_at).to eq(created_at) + expect(note.updated_at).to eq(updated_at) + expect(note.position.to_h).to eq({ + base_sha: merge_request.diffs.diff_refs.base_sha, + head_sha: merge_request.diffs.diff_refs.head_sha, + start_sha: merge_request.diffs.diff_refs.start_sha, + new_line: 15, + old_line: nil, + new_path: file_path, + old_path: file_path, + position_type: 'text', + line_range: nil + }) + expect(note.note) + .to eq <<~NOTE Suggestion: - ```suggestion + ```suggestion:-0+0 what do you think to do it like this ``` - EOB - end - - it 'imports the note' do - stub_user_finder(user.id, true) - - expect { subject.execute } - .to change(LegacyDiffNote, :count) - .and not_change(DiffNote, :count) - - note = project.notes.diff_notes.take - expect(note).to be_valid - expect(note.note) - .to eq <<~NOTE - Suggestion: - ```suggestion:-0+0 - what do you think to do it like this - ``` - NOTE - end - end - end - - context 'when github_importer_use_diff_note_with_suggestions is enabled' do - before do - stub_feature_flags(github_importer_use_diff_note_with_suggestions: true) + NOTE end - it_behaves_like 'diff notes without suggestion' + context 'when the note diff file creation fails' do + it 'falls back to the LegacyDiffNote' do + exception = ::DiffNote::NoteDiffFileCreationError.new('Failed to create diff note file') - context 'when the note has suggestions' do - let(:note_body) do - <<~EOB - Suggestion: - ```suggestion - what do you think to do it like this - ``` - EOB - end + expect_next_instance_of(::Import::Github::Notes::CreateService) do |service| + expect(service) + .to receive(:execute) + .and_raise(exception) + end - before do - stub_user_finder(user.id, true) - end + expect(Gitlab::GithubImport::Logger) + .to receive(:warn) + .with( + message: 'Failed to create diff note file', + 'error.class': 'DiffNote::NoteDiffFileCreationError' + ) - it 'imports the note as diff note' do expect { subject.execute } - .to change(DiffNote, :count) - .by(1) - - note = project.notes.diff_notes.take - expect(note).to be_valid - expect(note.noteable_type).to eq('MergeRequest') - expect(note.noteable_id).to eq(merge_request.id) - expect(note.project_id).to eq(project.id) - expect(note.author_id).to eq(user.id) - expect(note.system).to eq(false) - expect(note.discussion_id).to eq(discussion_id) - expect(note.commit_id).to eq('original123abc') - expect(note.line_code).to eq(note_representation.line_code) - expect(note.type).to eq('DiffNote') - expect(note.created_at).to eq(created_at) - expect(note.updated_at).to eq(updated_at) - expect(note.position.to_h).to eq({ - base_sha: merge_request.diffs.diff_refs.base_sha, - head_sha: merge_request.diffs.diff_refs.head_sha, - start_sha: merge_request.diffs.diff_refs.start_sha, - new_line: 15, - old_line: nil, - new_path: file_path, - old_path: file_path, - position_type: 'text', - line_range: nil - }) - expect(note.note) - .to eq <<~NOTE - Suggestion: - ```suggestion:-0+0 - what do you think to do it like this - ``` - NOTE - end - - context 'when the note diff file creation fails' do - it 'falls back to the LegacyDiffNote' do - exception = ::DiffNote::NoteDiffFileCreationError.new('Failed to create diff note file') - - expect_next_instance_of(::Import::Github::Notes::CreateService) do |service| - expect(service) - .to receive(:execute) - .and_raise(exception) - end - - expect(Gitlab::GithubImport::Logger) - .to receive(:warn) - .with( - message: 'Failed to create diff note file', - 'error.class': 'DiffNote::NoteDiffFileCreationError' - ) - - expect { subject.execute } - .to change(LegacyDiffNote, :count) - .and not_change(DiffNote, :count) - end + .to change(LegacyDiffNote, :count) + .and not_change(DiffNote, :count) end end end diff --git a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb index 1a25824bc8a..6b3d18f20e9 100644 --- a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb @@ -52,6 +52,12 @@ RSpec.describe Gitlab::GithubImport::Importer::ReleasesImporter do expect { importer.execute }.to change { Release.count }.by(1) end + + it 'is idempotent' do + allow(importer).to receive(:each_release).and_return([github_release]) + expect { importer.execute }.to change { Release.count }.by(1) + expect { importer.execute }.to change { Release.count }.by(0) # Idempotency check + end end describe '#build_releases' do @@ -79,6 +85,24 @@ RSpec.describe Gitlab::GithubImport::Importer::ReleasesImporter do expect(release[:description]).to eq('Release for tag 1.0') end + + it 'does not create releases that have a NULL tag' do + null_tag_release = double( + name: 'NULL Test', + tag_name: nil + ) + + expect(importer).to receive(:each_release).and_return([null_tag_release]) + expect(importer.build_releases).to be_empty + end + + it 'does not create duplicate release tags' do + expect(importer).to receive(:each_release).and_return([github_release, github_release]) + + releases = importer.build_releases + expect(releases.length).to eq(1) + expect(releases[0][:description]).to eq('This is my release') + end end describe '#build' do diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb index 58a8fb1b7e4..f2730ba74ec 100644 --- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb @@ -264,8 +264,8 @@ RSpec.describe Gitlab::GithubImport::Importer::RepositoryImporter do it 'sets the timestamp for when the cloning process finished' do freeze_time do expect(project) - .to receive(:update_column) - .with(:last_repository_updated_at, Time.zone.now) + .to receive(:touch) + .with(:last_repository_updated_at) importer.update_clone_time end diff --git a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb index 63834cfdb94..fe3040c102b 100644 --- a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb +++ b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_redis_shared_state do +RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_redis_cache do let(:hunk) do '@@ -1 +1 @@ -Hello @@ -166,6 +166,23 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_red expect(new_discussion_note.discussion_id) .to eq('SECOND_DISCUSSION_ID') end + + context 'when cached value does not exist' do + it 'falls back to generating a new discussion_id' do + expect(Discussion) + .to receive(:discussion_id) + .and_return('NEW_DISCUSSION_ID') + + reply_note = described_class.from_json_hash( + 'note_id' => note.note_id + 1, + 'in_reply_to_id' => note.note_id + ) + reply_note.project = project + reply_note.merge_request = merge_request + + expect(reply_note.discussion_id).to eq('NEW_DISCUSSION_ID') + end + end end end diff --git a/spec/lib/gitlab/gl_repository/identifier_spec.rb b/spec/lib/gitlab/gl_repository/identifier_spec.rb index e0622e30e7a..0a8559dd800 100644 --- a/spec/lib/gitlab/gl_repository/identifier_spec.rb +++ b/spec/lib/gitlab/gl_repository/identifier_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe Gitlab::GlRepository::Identifier do let_it_be(:project) { create(:project) } - let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) } - let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) } + let_it_be(:personal_snippet) { create(:personal_snippet, author: project.first_owner) } + let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.first_owner) } describe 'project repository' do it_behaves_like 'parsing gl_repository identifier' do diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb index 71a4c693f9d..0ec94563cbb 100644 --- a/spec/lib/gitlab/gl_repository/repo_type_spec.rb +++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::GlRepository::RepoType do let_it_be(:project) { create(:project) } - let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) } - let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) } + let_it_be(:personal_snippet) { create(:personal_snippet, author: project.first_owner) } + let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.first_owner) } let(:project_path) { project.repository.full_path } let(:wiki_path) { project.wiki.repository.full_path } diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb index b8ed4cf608d..047873d8237 100644 --- a/spec/lib/gitlab/gon_helper_spec.rb +++ b/spec/lib/gitlab/gon_helper_spec.rb @@ -6,9 +6,41 @@ RSpec.describe Gitlab::GonHelper do let(:helper) do Class.new do include Gitlab::GonHelper + + def current_user + nil + end end.new end + describe '#add_gon_variables' do + let(:gon) { double('gon').as_null_object } + let(:https) { true } + + before do + allow(helper).to receive(:gon).and_return(gon) + stub_config_setting(https: https) + end + + context 'when HTTPS is enabled' do + it 'sets the secure flag to true' do + expect(gon).to receive(:secure=).with(true) + + helper.add_gon_variables + end + end + + context 'when HTTP is enabled' do + let(:https) { false } + + it 'sets the secure flag to false' do + expect(gon).to receive(:secure=).with(false) + + helper.add_gon_variables + end + end + end + describe '#push_frontend_feature_flag' do before do skip_feature_flags_yaml_validation diff --git a/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb b/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb index 73e25f23848..274cc83a6be 100644 --- a/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe ::Gitlab::Graphql::Authorize::ObjectAuthorization do describe '#ok?' do diff --git a/spec/lib/gitlab/graphql/batch_key_spec.rb b/spec/lib/gitlab/graphql/batch_key_spec.rb index 7b73b27f24b..43e248885c2 100644 --- a/spec/lib/gitlab/graphql/batch_key_spec.rb +++ b/spec/lib/gitlab/graphql/batch_key_spec.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true require 'fast_spec_helper' -require 'test_prof/recipes/rspec/let_it_be' RSpec.describe ::Gitlab::Graphql::BatchKey do - let_it_be(:rect) { Struct.new(:len, :width) } - let_it_be(:circle) { Struct.new(:radius) } - + let(:rect) { Struct.new(:len, :width) } + let(:circle) { Struct.new(:radius) } let(:lookahead) { nil } let(:object) { rect.new(2, 3) } diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb index a3fb0bbbed8..c2253811e91 100644 --- a/spec/lib/gitlab/graphql/markdown_field_spec.rb +++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb @@ -73,7 +73,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do end it 'shows the reference to users that are allowed to see it' do - context = GraphQL::Query::Context.new(query: query, values: { current_user: project.owner }, object: nil) + context = GraphQL::Query::Context.new(query: query, values: { current_user: project.first_owner }, object: nil) type_instance = type_class.authorized_new(note, context) expect(field.to_graphql.resolve(type_instance, {}, context)).to include(issue_path(issue)) diff --git a/spec/lib/gitlab/graphql/queries_spec.rb b/spec/lib/gitlab/graphql/queries_spec.rb index 8b7f4ca7933..ad1aaac712e 100644 --- a/spec/lib/gitlab/graphql/queries_spec.rb +++ b/spec/lib/gitlab/graphql/queries_spec.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true require 'spec_helper' -require 'fast_spec_helper' -require "test_prof/recipes/rspec/let_it_be" RSpec.describe Gitlab::Graphql::Queries do shared_examples 'a valid GraphQL query for the blog schema' do diff --git a/spec/lib/gitlab/graphql/tracers/application_context_tracer_spec.rb b/spec/lib/gitlab/graphql/tracers/application_context_tracer_spec.rb index 6eff816b95a..264fe993197 100644 --- a/spec/lib/gitlab/graphql/tracers/application_context_tracer_spec.rb +++ b/spec/lib/gitlab/graphql/tracers/application_context_tracer_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require "fast_spec_helper" -require "support/graphql/fake_tracer" -require "support/graphql/fake_query_type" + +require 'spec_helper' RSpec.describe Gitlab::Graphql::Tracers::ApplicationContextTracer do let(:tracer_spy) { spy('tracer_spy') } diff --git a/spec/lib/gitlab/hook_data/project_builder_spec.rb b/spec/lib/gitlab/hook_data/project_builder_spec.rb index 672dbab918f..e86ac66b1ad 100644 --- a/spec/lib/gitlab/hook_data/project_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/project_builder_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Gitlab::HookData::ProjectBuilder do let(:attributes) do [ :event_name, :created_at, :updated_at, :name, :path, :path_with_namespace, :project_id, - :owner_name, :owner_email, :project_visibility + :owners, :owner_name, :owner_email, :project_visibility ] end @@ -30,6 +30,7 @@ RSpec.describe Gitlab::HookData::ProjectBuilder do expect(data[:project_id]).to eq(project.id) expect(data[:owner_name]).to eq('John') expect(data[:owner_email]).to eq('john@example.com') + expect(data[:owners]).to contain_exactly({ name: 'John', email: 'john@example.com' }) expect(data[:project_visibility]).to eq('internal') end end diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb index 7c57d162e9b..e9e517f1fe6 100644 --- a/spec/lib/gitlab/http_connection_adapter_spec.rb +++ b/spec/lib/gitlab/http_connection_adapter_spec.rb @@ -15,11 +15,33 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do stub_all_dns('https://example.org', ip_address: '93.184.216.34') end + context 'with use_read_total_timeout option' do + let(:options) { { use_read_total_timeout: true } } + + it 'sets up the connection using the Gitlab::NetHttpAdapter' do + expect(connection).to be_a(Gitlab::NetHttpAdapter) + expect(connection.address).to eq('93.184.216.34') + expect(connection.hostname_override).to eq('example.org') + expect(connection.addr_port).to eq('example.org') + expect(connection.port).to eq(443) + end + end + + context 'with header_read_timeout_buffered_io feature disabled' do + before do + stub_feature_flags(header_read_timeout_buffered_io: false) + end + + it 'uses the regular Net::HTTP class' do + expect(connection).to be_a(Net::HTTP) + end + end + context 'when local requests are allowed' do let(:options) { { allow_local_requests: true } } it 'sets up the connection' do - expect(connection).to be_a(Net::HTTP) + expect(connection).to be_a(Gitlab::NetHttpAdapter) expect(connection.address).to eq('93.184.216.34') expect(connection.hostname_override).to eq('example.org') expect(connection.addr_port).to eq('example.org') @@ -31,7 +53,7 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do let(:options) { { allow_local_requests: false } } it 'sets up the connection' do - expect(connection).to be_a(Net::HTTP) + expect(connection).to be_a(Gitlab::NetHttpAdapter) expect(connection.address).to eq('93.184.216.34') expect(connection.hostname_override).to eq('example.org') expect(connection.addr_port).to eq('example.org') @@ -52,7 +74,7 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do let(:options) { { allow_local_requests: true } } it 'sets up the connection' do - expect(connection).to be_a(Net::HTTP) + expect(connection).to be_a(Gitlab::NetHttpAdapter) expect(connection.address).to eq('172.16.0.0') expect(connection.hostname_override).to be(nil) expect(connection.addr_port).to eq('172.16.0.0') @@ -75,7 +97,7 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do let(:options) { { allow_local_requests: true } } it 'sets up the connection' do - expect(connection).to be_a(Net::HTTP) + expect(connection).to be_a(Gitlab::NetHttpAdapter) expect(connection.address).to eq('127.0.0.1') expect(connection.hostname_override).to be(nil) expect(connection.addr_port).to eq('127.0.0.1') @@ -88,7 +110,7 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do let(:uri) { URI('https://example.org:8080') } it 'sets up the addr_port accordingly' do - expect(connection).to be_a(Net::HTTP) + expect(connection).to be_a(Gitlab::NetHttpAdapter) expect(connection.address).to eq('93.184.216.34') expect(connection.hostname_override).to eq('example.org') expect(connection.addr_port).to eq('example.org:8080') @@ -103,7 +125,7 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do end it 'sets up the connection' do - expect(connection).to be_a(Net::HTTP) + expect(connection).to be_a(Gitlab::NetHttpAdapter) expect(connection.address).to eq('example.org') expect(connection.hostname_override).to eq(nil) expect(connection.addr_port).to eq('example.org') @@ -117,7 +139,7 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do end it 'sets up the connection' do - expect(connection).to be_a(Net::HTTP) + expect(connection).to be_a(Gitlab::NetHttpAdapter) expect(connection.address).to eq('example.org') expect(connection.hostname_override).to eq(nil) expect(connection.addr_port).to eq('example.org') diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb index 7d459f2d88a..7dbd21e6914 100644 --- a/spec/lib/gitlab/http_spec.rb +++ b/spec/lib/gitlab/http_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Gitlab::HTTP do end context 'when reading the response is too slow' do - before do + before(:all) do # Override Net::HTTP to add a delay between sending each response chunk mocked_http = Class.new(Net::HTTP) do def request(*) @@ -51,8 +51,17 @@ RSpec.describe Gitlab::HTTP do end @original_net_http = Net.send(:remove_const, :HTTP) + @webmock_net_http = WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_get('@webMockNetHTTP') + Net.send(:const_set, :HTTP, mocked_http) + WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_set('@webMockNetHTTP', mocked_http) + + # Reload Gitlab::NetHttpAdapter + Gitlab.send(:remove_const, :NetHttpAdapter) + load "#{Rails.root}/lib/gitlab/net_http_adapter.rb" + end + before do stub_const("#{described_class}::DEFAULT_READ_TOTAL_TIMEOUT", 0.001.seconds) WebMock.stub_request(:post, /.*/).to_return do |request| @@ -60,9 +69,14 @@ RSpec.describe Gitlab::HTTP do end end - after do + after(:all) do Net.send(:remove_const, :HTTP) Net.send(:const_set, :HTTP, @original_net_http) + WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_set('@webMockNetHTTP', @webmock_net_http) + + # Reload Gitlab::NetHttpAdapter + Gitlab.send(:remove_const, :NetHttpAdapter) + load "#{Rails.root}/lib/gitlab/net_http_adapter.rb" end let(:options) { {} } diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index f4a112d35aa..ce13f405459 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -529,6 +529,7 @@ project: - vulnerability_feedback - vulnerability_identifiers - vulnerability_scanners +- dast_profiles - dast_site_profiles - dast_scanner_profiles - dast_sites @@ -605,6 +606,7 @@ project: - ci_project_mirror - sync_events - secure_files +- security_trainings award_emoji: - awardable - user diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb index 31512259bb1..738a76d3360 100644 --- a/spec/lib/gitlab/import_export/command_line_util_spec.rb +++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb @@ -97,7 +97,7 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do include Gitlab::ImportExport::CommandLineUtil end.new - expect { klass.tar_cf(archive: 'test', dir: 'test') }.to raise_error(Gitlab::ImportExport::Error, 'System call failed') + expect { klass.tar_cf(archive: 'test', dir: 'test') }.to raise_error(Gitlab::ImportExport::Error, 'command exited with error code 1: Error') end end end @@ -125,14 +125,31 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do end context 'when something goes wrong' do - it 'raises an error' do + before do expect(Gitlab::Popen).to receive(:popen).and_return(['Error', 1]) + end + it 'raises an error' do klass = Class.new do include Gitlab::ImportExport::CommandLineUtil end.new - expect { klass.untar_xf(archive: 'test', dir: 'test') }.to raise_error(Gitlab::ImportExport::Error, 'System call failed') + expect { klass.untar_xf(archive: 'test', dir: 'test') }.to raise_error(Gitlab::ImportExport::Error, 'command exited with error code 1: Error') + end + + it 'returns false and includes error status' do + klass = Class.new do + include Gitlab::ImportExport::CommandLineUtil + + attr_accessor :shared + + def initialize + @shared = Gitlab::ImportExport::Shared.new(nil) + end + end.new + + expect(klass.tar_czf(archive: 'test', dir: 'test')).to eq(false) + expect(klass.shared.errors).to eq(['command exited with error code 1: Error']) end end end diff --git a/spec/lib/gitlab/import_export/config_spec.rb b/spec/lib/gitlab/import_export/config_spec.rb index 7ad5d3d846c..fcb48678b88 100644 --- a/spec/lib/gitlab/import_export/config_spec.rb +++ b/spec/lib/gitlab/import_export/config_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'rspec-parameterized' +require 'spec_helper' RSpec.describe Gitlab::ImportExport::Config do let(:yaml_file) { described_class.new } diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb index adb613c3abc..ce888b71d5e 100644 --- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb +++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb @@ -240,7 +240,7 @@ RSpec.describe Gitlab::ImportExport::FastHashSerializer do merge_request = create(:merge_request, source_project: project, milestone: milestone) ci_build = create(:ci_build, project: project, when: nil) - ci_build.pipeline.update(project: project) + ci_build.pipeline.update!(project: project) create(:commit_status, project: project, pipeline: ci_build.pipeline) create_list(:ci_pipeline, 5, :success, project: project) diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index 65c28a8b8a2..25c82588c13 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -38,8 +38,8 @@ RSpec.describe 'forked project import' do allow(instance).to receive(:storage_path).and_return(export_path) end - saver.save - repo_saver.save + saver.save # rubocop:disable Rails/SaveBang + repo_saver.save # rubocop:disable Rails/SaveBang repo_restorer.restore restorer.restore diff --git a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb index e075c5acfea..31d647f883a 100644 --- a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do # ^ These are specific for the Group::LegacyTreeSaver context 'JSON' do let(:saved_group_json) do - group_tree_saver.save + group_tree_saver.save # rubocop:disable Rails/SaveBang group_json(group_tree_saver.full_path) end @@ -88,7 +88,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do end before do - user2.update(public_email: user2.email) + user2.update!(public_email: user2.email) group.add_developer(user2) end diff --git a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb index 63286fc0719..8e7fe8849d4 100644 --- a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Gitlab::ImportExport::Group::RelationFactory do let(:importer_user) { admin } let(:excluded_keys) { [] } let(:created_object) do - described_class.create( + described_class.create( # rubocop:disable Rails/SaveBang relation_sym: relation_sym, relation_hash: relation_hash, relation_index: 1, diff --git a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb index c52daa8ccfd..de4d193a21c 100644 --- a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb @@ -42,7 +42,7 @@ RSpec.describe Gitlab::ImportExport::Group::TreeSaver do context 'exported files' do before do - group_tree_saver.save + group_tree_saver.save # rubocop:disable Rails/SaveBang end it 'has one group per line' do diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb index 20f0f6af6f3..c9d559c992c 100644 --- a/spec/lib/gitlab/import_export/importer_spec.rb +++ b/spec/lib/gitlab/import_export/importer_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Gitlab::ImportExport::Importer do stub_uploads_object_storage(FileUploader) FileUtils.mkdir_p(shared.export_path) - ImportExportUpload.create(project: project, import_file: import_file) + ImportExportUpload.create!(project: project, import_file: import_file) allow(FileUtils).to receive(:rm_rf).and_call_original end diff --git a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb index d69d775fffb..352af18c822 100644 --- a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb +++ b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb @@ -183,24 +183,8 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do end describe '.batch_size' do - context 'when export_reduce_relation_batch_size feature flag is enabled' do - before do - stub_feature_flags(export_reduce_relation_batch_size: true) - end - - it 'returns 20' do - expect(described_class.batch_size(exportable)).to eq(described_class::SMALLER_BATCH_SIZE) - end - end - - context 'when export_reduce_relation_batch_size feature flag is disabled' do - before do - stub_feature_flags(export_reduce_relation_batch_size: false) - end - - it 'returns default batch size' do - expect(described_class.batch_size(exportable)).to eq(described_class::BATCH_SIZE) - end + it 'returns default batch size' do + expect(described_class.batch_size(exportable)).to eq(described_class::BATCH_SIZE) end end end diff --git a/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb index 3b7ed7cb32b..0d372def8b0 100644 --- a/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb @@ -8,35 +8,17 @@ RSpec.describe Gitlab::ImportExport::LegacyRelationTreeSaver do let(:tree) { {} } describe '#serialize' do - shared_examples 'FastHashSerializer with batch size' do |batch_size| - let(:serializer) { instance_double(Gitlab::ImportExport::FastHashSerializer) } + let(:serializer) { instance_double(Gitlab::ImportExport::FastHashSerializer) } - it 'uses FastHashSerializer' do - expect(Gitlab::ImportExport::FastHashSerializer) - .to receive(:new) - .with(exportable, tree, batch_size: batch_size) - .and_return(serializer) + it 'uses FastHashSerializer' do + expect(Gitlab::ImportExport::FastHashSerializer) + .to receive(:new) + .with(exportable, tree, batch_size: Gitlab::ImportExport::Json::StreamingSerializer::BATCH_SIZE) + .and_return(serializer) - expect(serializer).to receive(:execute) + expect(serializer).to receive(:execute) - relation_tree_saver.serialize(exportable, tree) - end - end - - context 'when export_reduce_relation_batch_size feature flag is enabled' do - before do - stub_feature_flags(export_reduce_relation_batch_size: true) - end - - include_examples 'FastHashSerializer with batch size', Gitlab::ImportExport::Json::StreamingSerializer::SMALLER_BATCH_SIZE - end - - context 'when export_reduce_relation_batch_size feature flag is disabled' do - before do - stub_feature_flags(export_reduce_relation_batch_size: false) - end - - include_examples 'FastHashSerializer with batch size', Gitlab::ImportExport::Json::StreamingSerializer::BATCH_SIZE + relation_tree_saver.serialize(exportable, tree) end end end diff --git a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb index c8887b0ded1..fe064c50b9e 100644 --- a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Gitlab::ImportExport::LfsRestorer do ) end - saver.save + saver.save # rubocop:disable Rails/SaveBang project.lfs_objects.delete_all end @@ -81,7 +81,7 @@ RSpec.describe Gitlab::ImportExport::LfsRestorer do context 'when there is not an existing `LfsObject`' do before do - lfs_object.destroy + lfs_object.destroy! end it 'creates a new lfs object' do diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb index 55b4f7479b8..84bd782c467 100644 --- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb +++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb @@ -34,13 +34,13 @@ RSpec.describe Gitlab::ImportExport::LfsSaver do end it 'does not cause errors' do - saver.save + saver.save # rubocop:disable Rails/SaveBang expect(shared.errors).to be_empty end it 'copies the file in the correct location when there is an lfs object' do - saver.save + saver.save # rubocop:disable Rails/SaveBang expect(File).to exist("#{shared.export_path}/lfs-objects/#{lfs_object.oid}") end @@ -61,7 +61,7 @@ RSpec.describe Gitlab::ImportExport::LfsSaver do end it 'saves a json file correctly' do - saver.save + saver.save # rubocop:disable Rails/SaveBang expect(File.exist?(lfs_json_file)).to eq(true) expect(lfs_json).to eq( @@ -96,7 +96,7 @@ RSpec.describe Gitlab::ImportExport::LfsSaver do expect(fake_uri).to receive(:open).and_return(StringIO.new('LFS file content')) expect(URI).to receive(:parse).with('http://my-object-storage.local').and_return(fake_uri) - saver.save + saver.save # rubocop:disable Rails/SaveBang expect(File.read(exported_file_path)).to eq('LFS file content') end diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index 8b9ca90a280..8d9bff9c610 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -243,7 +243,6 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do before do group.add_users([user, user2], GroupMember::DEVELOPER) - user.update(public_email: 'invite@test.com') end it 'maps the importer' do diff --git a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb index ea8b10675af..ffbbf9326ec 100644 --- a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_ let(:importer_user) { admin } let(:excluded_keys) { [] } let(:created_object) do - described_class.create( + described_class.create( # rubocop:disable Rails/SaveBang relation_sym: relation_sym, relation_hash: relation_hash, relation_index: 1, diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb index 1d8b137c196..8884722254d 100644 --- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb @@ -880,7 +880,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do before do group = create(:group, visibility_level: group_visibility) group.add_users([user], GroupMember::MAINTAINER) - project.update(group: group) + project.update!(group: group) end context 'private group visibility' do diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb index f68ec21039d..ba781ae78b7 100644 --- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do project_tree_saver = described_class.new(project: project, current_user: user, shared: shared) - project_tree_saver.save + project_tree_saver.save # rubocop:disable Rails/SaveBang end end @@ -305,14 +305,14 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do end before do - user2.update(public_email: user2.email) + user2.update!(public_email: user2.email) group.add_developer(user2) end context 'when has no permission' do before do group.add_developer(user) - project_tree_saver.save + project_tree_saver.save # rubocop:disable Rails/SaveBang end it 'does not export group members' do @@ -324,7 +324,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do before do group.add_maintainer(user) - project_tree_saver.save + project_tree_saver.save # rubocop:disable Rails/SaveBang end it 'does not export group members' do @@ -336,7 +336,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do before do group.add_owner(user) - project_tree_saver.save + project_tree_saver.save # rubocop:disable Rails/SaveBang end it 'exports group members as group owner' do @@ -348,7 +348,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do let(:user) { create(:admin) } before do - project_tree_saver.save + project_tree_saver.save # rubocop:disable Rails/SaveBang end context 'when admin mode is enabled', :enable_admin_mode do @@ -376,7 +376,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do let(:relation_name) { :projects } before do - project_tree_saver.save + project_tree_saver.save # rubocop:disable Rails/SaveBang end it { is_expected.to include({ 'description' => params[:description] }) } @@ -471,7 +471,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do merge_request = create(:merge_request, source_project: project, milestone: milestone) ci_build = create(:ci_build, project: project, when: nil) - ci_build.pipeline.update(project: project) + ci_build.pipeline.update!(project: project) create(:commit_status, project: project, pipeline: ci_build.pipeline) create(:milestone, project: project) diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index 718a23f80a1..c0215ff5843 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -19,7 +19,7 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do before do allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) - bundler.save + bundler.save # rubocop:disable Rails/SaveBang end after do diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb index 877474dd862..f5eed81f73c 100644 --- a/spec/lib/gitlab/import_export/saver_spec.rb +++ b/spec/lib/gitlab/import_export/saver_spec.rb @@ -30,19 +30,63 @@ RSpec.describe Gitlab::ImportExport::Saver do it 'saves the repo using object storage' do stub_uploads_object_storage(ImportExportUploader) - subject.save + subject.save # rubocop:disable Rails/SaveBang expect(ImportExportUpload.find_by(project: project).export_file.url) .to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*]) end + it 'logs metrics after saving' do + stub_uploads_object_storage(ImportExportUploader) + expect(Gitlab::Export::Logger).to receive(:info).with( + hash_including( + message: 'Export archive saved', + exportable_class: 'Project', + 'correlation_id' => anything, + archive_file: anything, + compress_duration_s: anything + )).and_call_original + + expect(Gitlab::Export::Logger).to receive(:info).with( + hash_including( + message: 'Export archive uploaded', + exportable_class: 'Project', + 'correlation_id' => anything, + archive_file: anything, + compress_duration_s: anything, + assign_duration_s: anything, + upload_duration_s: anything, + upload_bytes: anything + )).and_call_original + + subject.save # rubocop:disable Rails/SaveBang + end + it 'removes archive path and keeps base path untouched' do allow(shared).to receive(:archive_path).and_return(archive_path) - subject.save + subject.save # rubocop:disable Rails/SaveBang expect(FileUtils).not_to have_received(:rm_rf).with(base_path) expect(FileUtils).to have_received(:rm_rf).with(archive_path) expect(Dir.exist?(archive_path)).to eq(false) end + + context 'when save throws an exception' do + before do + expect(subject).to receive(:save_upload).and_raise(SocketError.new) + end + + it 'logs a saver error' do + allow(Gitlab::Export::Logger).to receive(:info).with(anything).and_call_original + expect(Gitlab::Export::Logger).to receive(:info).with( + hash_including( + message: 'Export archive saver failed', + exportable_class: 'Project', + 'correlation_id' => anything + )).and_call_original + + subject.save # rubocop:disable Rails/SaveBang + end + end end diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb index 7d719b6028f..2f39cb560d0 100644 --- a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb @@ -70,7 +70,7 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoRestorer do let!(:snippet_with_repo) { create(:project_snippet, :repository, project: project, author: user) } let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) } let(:snippet_bundle_path) { File.join(bundle_path, "#{snippet_with_repo.hexdigest}.bundle") } - let(:result) { exporter.save } + let(:result) { exporter.save } # rubocop:disable Rails/SaveBang let(:repository) { snippet.repository } before do diff --git a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb index 9f3e8d2fa86..9a9f40b3d0c 100644 --- a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb @@ -38,7 +38,7 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoSaver do aggregate_failures do expect(snippet.repository).not_to receive(:bundle_to_disk) - bundler.save + bundler.save # rubocop:disable Rails/SaveBang expect(Dir.empty?(bundle_path)).to be_truthy end diff --git a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb index 7ca365762b5..e529d36fd11 100644 --- a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb @@ -64,7 +64,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoRestorer do let!(:snippet2) { create(:project_snippet, project: project, author: user) } before do - exporter.save + exporter.save # rubocop:disable Rails/SaveBang expect(File.exist?(bundle_path(snippet1))).to be true expect(File.exist?(bundle_path(snippet2))).to be false @@ -78,7 +78,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoRestorer do let!(:snippet2) { create(:project_snippet, :repository, project: project, author: user) } before do - exporter.save + exporter.save # rubocop:disable Rails/SaveBang expect(File.exist?(bundle_path(snippet1))).to be true expect(File.exist?(bundle_path(snippet2))).to be true diff --git a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb index aa284c60e73..eaa58c77aff 100644 --- a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do snippets_dir = ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) expect(Dir.exist?(snippets_dir)).to be_falsey - bundler.save + bundler.save # rubocop:disable Rails/SaveBang expect(Dir.exist?(snippets_dir)).to be_truthy end @@ -27,7 +27,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do it 'does not perform any action' do expect(Gitlab::ImportExport::SnippetRepoSaver).not_to receive(:new) - bundler.save + bundler.save # rubocop:disable Rails/SaveBang end end @@ -40,7 +40,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do allow(Gitlab::ImportExport::SnippetRepoSaver).to receive(:new).and_return(service) expect(service).to receive(:save).and_return(true).twice - bundler.save + bundler.save # rubocop:disable Rails/SaveBang end context 'when one snippet cannot be saved' do diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb index 8282ad9a070..0cfe3a69a09 100644 --- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb @@ -31,13 +31,13 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do let(:upload) { create(:upload, :issuable_upload, :with_file, model: project) } it 'does not cause errors' do - manager.save + manager.save # rubocop:disable Rails/SaveBang expect(shared.errors).to be_empty end it 'copies the file in the correct location when there is an upload' do - manager.save + manager.save # rubocop:disable Rails/SaveBang expect(File).to exist(exported_file_path) end @@ -56,7 +56,7 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do end it 'excludes orphaned upload files' do - manager.save + manager.save # rubocop:disable Rails/SaveBang expect(File).not_to exist(exported_orphan_path) end @@ -68,7 +68,7 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do end it 'does not cause errors' do - manager.save + manager.save # rubocop:disable Rails/SaveBang expect(shared.errors).to be_empty end @@ -84,7 +84,7 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do it 'ignores problematic upload and logs exception' do expect(Gitlab::ErrorTracking).to receive(:log_exception).with(instance_of(Errno::ENAMETOOLONG), project_id: project.id) - manager.save + manager.save # rubocop:disable Rails/SaveBang expect(shared.errors).to be_empty expect(File).not_to exist(exported_file_path) diff --git a/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb b/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb index 535cce6aa04..c5288b9afbc 100644 --- a/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb +++ b/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::IncidentManagement::PagerDuty::IncidentIssueDescription do describe '#to_s' do diff --git a/spec/lib/gitlab/json_spec.rb b/spec/lib/gitlab/json_spec.rb index f9f57752b0a..5ffe736da54 100644 --- a/spec/lib/gitlab/json_spec.rb +++ b/spec/lib/gitlab/json_spec.rb @@ -317,36 +317,14 @@ RSpec.describe Gitlab::Json do let(:env) { {} } let(:result) { "{\"test\":true}" } - context "grape_gitlab_json flag is enabled" do - before do - stub_feature_flags(grape_gitlab_json: true) - end - - it "generates JSON" do - expect(subject).to eq(result) - end - - it "uses Gitlab::Json" do - expect(Gitlab::Json).to receive(:dump).with(obj) - - subject - end + it "generates JSON" do + expect(subject).to eq(result) end - context "grape_gitlab_json flag is disabled" do - before do - stub_feature_flags(grape_gitlab_json: false) - end + it "uses Gitlab::Json" do + expect(Gitlab::Json).to receive(:dump).with(obj) - it "generates JSON" do - expect(subject).to eq(result) - end - - it "uses Grape::Formatter::Json" do - expect(Grape::Formatter::Json).to receive(:call).with(obj, env) - - subject - end + subject end context "precompiled JSON" do @@ -440,15 +418,5 @@ RSpec.describe Gitlab::Json do expect(subject.size).to eq(10001) end end - - context 'when json_limited_encoder is disabled' do - let(:obj) { [{ test: true }] * 1000 } - - it 'does not raise an error' do - stub_feature_flags(json_limited_encoder: false) - - expect { subject }.not_to raise_error - end - end end end diff --git a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb index 2e373613269..3028e0a13aa 100644 --- a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb +++ b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb @@ -2,6 +2,8 @@ require 'fast_spec_helper' +require_relative '../../../../lib/gitlab/kubernetes/pod_cmd' + RSpec.describe Gitlab::Kubernetes::KubectlCmd do describe '.delete' do it 'constructs string properly' do diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb index 73b35d3a4e7..cbd1a30c417 100644 --- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb @@ -63,5 +63,13 @@ RSpec.describe Gitlab::LegacyGithubImport::ReleaseFormatter do expect(release.valid?).to eq false end end + + context 'when release has NULL tag' do + let(:raw_data) { double(base_data.merge(tag_name: '')) } + + it 'returns false' do + expect(release.valid?).to eq false + end + end end end diff --git a/spec/lib/gitlab/metrics/boot_time_tracker_spec.rb b/spec/lib/gitlab/metrics/boot_time_tracker_spec.rb new file mode 100644 index 00000000000..8a17fa8dd2e --- /dev/null +++ b/spec/lib/gitlab/metrics/boot_time_tracker_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Gitlab::Metrics::BootTimeTracker do + let(:logger) { double('logger') } + let(:gauge) { double('gauge') } + + subject(:tracker) { described_class.instance } + + before do + described_class.instance.reset! + + allow(logger).to receive(:info) + allow(gauge).to receive(:set) + allow(Gitlab::Metrics).to receive(:gauge).and_return(gauge) + end + + describe '#track_boot_time!' do + described_class::SUPPORTED_RUNTIMES.each do |runtime| + context "when called on #{runtime} for the first time" do + before do + expect(Gitlab::Runtime).to receive(:safe_identify).and_return(runtime) + end + + it 'set the startup_time' do + tracker.track_boot_time!(logger: logger) + + expect(tracker.startup_time).to be > 0 + end + + it 'records the current process runtime' do + expect(Gitlab::Metrics::System).to receive(:process_runtime_elapsed_seconds).once + + tracker.track_boot_time!(logger: logger) + end + + it 'logs the application boot time' do + expect(Gitlab::Metrics::System).to receive(:process_runtime_elapsed_seconds).and_return(42) + expect(logger).to receive(:info).with(message: 'Application boot finished', runtime: runtime.to_s, duration_s: 42) + + tracker.track_boot_time!(logger: logger) + end + + it 'tracks boot time in a prometheus gauge' do + expect(Gitlab::Metrics::System).to receive(:process_runtime_elapsed_seconds).and_return(42) + expect(gauge).to receive(:set).with({}, 42) + + tracker.track_boot_time!(logger: logger) + end + + context 'on subsequent calls' do + it 'does nothing' do + tracker.track_boot_time!(logger: logger) + + expect(Gitlab::Metrics::System).not_to receive(:process_runtime_elapsed_seconds) + expect(logger).not_to receive(:info) + expect(gauge).not_to receive(:set) + + tracker.track_boot_time!(logger: logger) + end + end + end + end + + context 'when called on other runtimes' do + it 'does nothing' do + tracker.track_boot_time!(logger: logger) + + expect(Gitlab::Metrics::System).not_to receive(:process_runtime_elapsed_seconds) + expect(logger).not_to receive(:info) + expect(gauge).not_to receive(:set) + + tracker.track_boot_time!(logger: logger) + end + end + end + + describe '#startup_time' do + it 'returns 0 when boot time not tracked' do + expect(tracker.startup_time).to eq(0) + end + end +end diff --git a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb deleted file mode 100644 index 0531bccf4b4..00000000000 --- a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Metrics::Exporter::WebExporter do - let(:exporter) { described_class.new } - let(:readiness_probe) { exporter.send(:readiness_probe).execute } - - before do - stub_config( - monitoring: { - web_exporter: { - enabled: true, - port: 0, - address: '127.0.0.1' - } - } - ) - - exporter.start - end - - after do - exporter.stop - end - - context 'when running server', :prometheus do - it 'readiness probe returns succesful status' do - expect(readiness_probe.http_status).to eq(200) - expect(readiness_probe.json).to include(status: 'ok') - expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'ok' }]) - end - - it 'initializes request metrics' do - expect(Gitlab::Metrics::RailsSlis).to receive(:initialize_request_slis_if_needed!).and_call_original - - http = Net::HTTP.new(exporter.server.config[:BindAddress], exporter.server.config[:Port]) - response = http.request(Net::HTTP::Get.new('/metrics')) - - expect(response.body).to include('gitlab_sli:rails_request_apdex') - end - end - - describe '#mark_as_not_running!' do - it 'readiness probe returns a failure status', :prometheus do - exporter.mark_as_not_running! - - expect(readiness_probe.http_status).to eq(503) - expect(readiness_probe.json).to include(status: 'failed') - expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'failed' }]) - end - end -end diff --git a/spec/lib/gitlab/metrics/rails_slis_spec.rb b/spec/lib/gitlab/metrics/rails_slis_spec.rb index a5ccf7fafa4..0c77dc9f582 100644 --- a/spec/lib/gitlab/metrics/rails_slis_spec.rb +++ b/spec/lib/gitlab/metrics/rails_slis_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Gitlab::Metrics::RailsSlis do allow(Gitlab::Graphql::KnownOperations).to receive(:default).and_return(Gitlab::Graphql::KnownOperations.new(%w(foo bar))) end - describe '.initialize_request_slis_if_needed!' do + describe '.initialize_request_slis!' do it "initializes the SLI for all possible endpoints if they weren't", :aggregate_failures do possible_labels = [ { @@ -41,7 +41,7 @@ RSpec.describe Gitlab::Metrics::RailsSlis do expect(Gitlab::Metrics::Sli).to receive(:initialize_sli).with(:rails_request_apdex, array_including(*possible_labels)).and_call_original expect(Gitlab::Metrics::Sli).to receive(:initialize_sli).with(:graphql_query_apdex, array_including(*possible_graphql_labels)).and_call_original - described_class.initialize_request_slis_if_needed! + described_class.initialize_request_slis! end it 'does not initialize the SLI if they were initialized already', :aggregate_failures do @@ -49,13 +49,13 @@ RSpec.describe Gitlab::Metrics::RailsSlis do expect(Gitlab::Metrics::Sli).to receive(:initialized?).with(:graphql_query_apdex) { true } expect(Gitlab::Metrics::Sli).not_to receive(:initialize_sli) - described_class.initialize_request_slis_if_needed! + described_class.initialize_request_slis! end end describe '.request_apdex' do it 'returns the initialized request apdex SLI object' do - described_class.initialize_request_slis_if_needed! + described_class.initialize_request_slis! expect(described_class.request_apdex).to be_initialized end @@ -63,7 +63,7 @@ RSpec.describe Gitlab::Metrics::RailsSlis do describe '.graphql_query_apdex' do it 'returns the initialized request apdex SLI object' do - described_class.initialize_request_slis_if_needed! + described_class.initialize_request_slis! expect(described_class.graphql_query_apdex).to be_initialized end diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb index a4877208bcf..dfae5aa6784 100644 --- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb @@ -18,6 +18,20 @@ RSpec.describe Gitlab::Metrics::Samplers::RubySampler do expect(sampler.metrics[:process_start_time_seconds].get).to eq(Time.now.to_i) end end + + context 'when not setting a prefix' do + it 'does not prepend metrics with that prefix' do + expect(sampler.metrics[:process_start_time_seconds].name).to eq(:ruby_process_start_time_seconds) + end + end + + context 'when using custom prefix' do + let(:sampler) { described_class.new(prefix: 'custom') } + + it 'prepends metrics with that prefix' do + expect(sampler.metrics[:process_start_time_seconds].name).to eq(:custom_ruby_process_start_time_seconds) + end + end end describe '#sample' do diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index 732aa553737..ce3caf8cdfe 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -4,6 +4,13 @@ require 'spec_helper' RSpec.describe Gitlab::Metrics::System do context 'when /proc files exist' do + # Modified column 22 to be 1000 (starttime ticks) + let(:proc_stat) do + <<~SNIP + 2095 (ruby) R 0 2095 2095 34818 2095 4194560 211267 7897 2 0 287 51 10 1 20 0 5 0 1000 566210560 80885 18446744073709551615 94736211292160 94736211292813 140720919612064 0 0 0 0 0 1107394127 0 0 0 17 3 0 0 0 0 0 94736211303768 94736211304544 94736226689024 140720919619473 140720919619513 140720919619513 140720919621604 0 + SNIP + end + # Fixtures pulled from: # Linux carbon 5.3.0-7648-generic #41~1586789791~19.10~9593806-Ubuntu SMP Mon Apr 13 17:50:40 UTC x86_64 x86_64 x86_64 GNU/Linux let(:proc_status) do @@ -97,6 +104,29 @@ RSpec.describe Gitlab::Metrics::System do end end + describe '.process_runtime_elapsed_seconds' do + it 'returns the seconds elapsed since the process was started' do + # sets process starttime ticks to 1000 + mock_existing_proc_file('/proc/self/stat', proc_stat) + # system clock ticks/sec + expect(Etc).to receive(:sysconf).with(Etc::SC_CLK_TCK).and_return(100) + # system uptime in seconds + expect(::Process).to receive(:clock_gettime).and_return(15) + + # uptime - (starttime_ticks / ticks_per_sec) + expect(described_class.process_runtime_elapsed_seconds).to eq(5) + end + + context 'when inputs are not available' do + it 'returns 0' do + mock_missing_proc_file + expect(::Process).to receive(:clock_gettime).and_raise(NameError) + + expect(described_class.process_runtime_elapsed_seconds).to eq(0) + end + end + end + describe '.summary' do it 'contains a selection of the available fields' do stub_const('RUBY_DESCRIPTION', 'ruby-3.0-patch1') @@ -223,10 +253,10 @@ RSpec.describe Gitlab::Metrics::System do end def mock_existing_proc_file(path, content) - allow(File).to receive(:foreach).with(path) { |_path, &block| content.each_line(&block) } + allow(File).to receive(:open).with(path) { |_path, &block| block.call(StringIO.new(content)) } end def mock_missing_proc_file - allow(File).to receive(:foreach).and_raise(Errno::ENOENT) + allow(File).to receive(:open).and_raise(Errno::ENOENT) end end diff --git a/spec/lib/gitlab/middleware/memory_report_spec.rb b/spec/lib/gitlab/middleware/memory_report_spec.rb new file mode 100644 index 00000000000..e063866b056 --- /dev/null +++ b/spec/lib/gitlab/middleware/memory_report_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'memory_profiler' + +RSpec.describe Gitlab::Middleware::MemoryReport do + let(:app) { proc { |env| [200, { 'Content-Type' => 'text/plain' }, ['Hello world!']] } } + let(:middleware) { described_class.new(app) } + + describe '#call' do + shared_examples 'returns original response' do + it 'returns original response' do + expect(MemoryProfiler).not_to receive(:report) + + status, headers, body = middleware.call(env) + + expect(status).to eq(200) + expect(headers).to eq({ 'Content-Type' => 'text/plain' }) + expect(body.first).to eq('Hello world!') + end + + it 'does not call the MemoryProfiler' do + expect(MemoryProfiler).not_to receive(:report) + + middleware.call(env) + end + end + + context 'when the Rails environment is not development' do + let(:env) { Rack::MockRequest.env_for('/') } + + it_behaves_like 'returns original response' + end + + context 'when the Rails environment is development' do + before do + allow(Rails.env).to receive(:development?).and_return(true) + end + + context 'when memory report is not requested' do + let(:env) { Rack::MockRequest.env_for('/') } + + it_behaves_like 'returns original response' + end + + context 'when memory report is requested' do + let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'memory' }) } + + before do + allow(env).to receive(:[]).and_call_original + allow(app).to receive(:call).and_return(empty_memory_report) + end + + let(:empty_memory_report) do + report = MemoryProfiler::Results.new + report.register_results(MemoryProfiler::StatHash.new, MemoryProfiler::StatHash.new, 1) + end + + it 'returns a memory report' do + expect(MemoryProfiler).to receive(:report).and_yield + + status, headers, body = middleware.call(env) + + expect(status).to eq(200) + expect(headers).to eq({ 'Content-Type' => 'text/plain' }) + expect(body.first).to include('Total allocated: 0 B') + end + + context 'when something goes wrong with creating the report' do + before do + expect(MemoryProfiler).to receive(:report).and_raise(StandardError, 'something went terribly wrong!') + end + + it 'logs the error' do + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + + middleware.call(env) + end + + it 'returns the error' do + status, headers, body = middleware.call(env) + + expect(status).to eq(500) + expect(headers).to eq({ 'Content-Type' => 'text/plain' }) + expect(body.first).to include('Could not generate memory report: something went terribly wrong!') + end + end + end + end + end +end diff --git a/spec/lib/gitlab/net_http_adapter_spec.rb b/spec/lib/gitlab/net_http_adapter_spec.rb new file mode 100644 index 00000000000..21c1a1ebe25 --- /dev/null +++ b/spec/lib/gitlab/net_http_adapter_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::NetHttpAdapter do + describe '#connect' do + let(:url) { 'https://example.org' } + let(:net_http_adapter) { described_class.new(url) } + + subject(:connect) { net_http_adapter.send(:connect) } + + before do + allow(TCPSocket).to receive(:open).and_return(Socket.new(:INET, :STREAM)) + end + + it 'uses a Gitlab::BufferedIo instance as @socket' do + connect + + expect(net_http_adapter.instance_variable_get(:@socket)).to be_a(Gitlab::BufferedIo) + end + end +end diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb index 577d15b8495..42ae5844b95 100644 --- a/spec/lib/gitlab/omniauth_initializer_spec.rb +++ b/spec/lib/gitlab/omniauth_initializer_spec.rb @@ -101,5 +101,19 @@ RSpec.describe Gitlab::OmniauthInitializer do subject.execute([google_config]) end + + it 'configures defaults for gitlab' do + conf = { + 'name' => 'gitlab', + "args" => {} + } + + expect(devise_config).to receive(:omniauth).with( + :gitlab, + authorize_params: { gl_auth_type: 'login' } + ) + + subject.execute([conf]) + end end end diff --git a/spec/lib/gitlab/pagination/keyset/in_operator_optimization/array_scope_columns_spec.rb b/spec/lib/gitlab/pagination/keyset/in_operator_optimization/array_scope_columns_spec.rb index 2cebf0d9473..087bfb197ec 100644 --- a/spec/lib/gitlab/pagination/keyset/in_operator_optimization/array_scope_columns_spec.rb +++ b/spec/lib/gitlab/pagination/keyset/in_operator_optimization/array_scope_columns_spec.rb @@ -16,4 +16,13 @@ RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::ArrayScopeCol it { expect { array_scope_columns }.to raise_error /No array columns were given/ } end + + context 'when Arel AS node is given as input' do + let(:scope) { Issue.select(Issue.arel_table[:id].as('id'), :title) } + let(:columns) { scope.select_values } + + it 'works with Arel AS nodes' do + expect(array_scope_columns.array_aggregated_column_names).to eq(%w[array_cte_id_array array_cte_title_array]) + end + end end diff --git a/spec/lib/gitlab/pipeline_scope_counts_spec.rb b/spec/lib/gitlab/pipeline_scope_counts_spec.rb new file mode 100644 index 00000000000..a9187ecfb54 --- /dev/null +++ b/spec/lib/gitlab/pipeline_scope_counts_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::PipelineScopeCounts do + let(:current_user) { create(:user) } + + let_it_be(:project) { create(:project, :private) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) } + let_it_be(:success_pipeline) { create(:ci_pipeline, :success, project: project) } + let_it_be(:ref_pipeline) { create(:ci_pipeline, project: project, ref: 'awesome-feature') } + let_it_be(:sha_pipeline) { create(:ci_pipeline, :running, project: project, sha: 'deadbeef') } + let_it_be(:on_demand_dast_scan) { create(:ci_pipeline, :success, project: project, source: 'ondemand_dast_scan') } + + before do + project.add_developer(current_user) + end + + it 'has policy class' do + expect(described_class.declarative_policy_class).to be("Ci::ProjectPipelinesPolicy") + end + + it 'has expected attributes' do + expect(described_class.new(current_user, project, {})).to have_attributes( + all: 6, + finished: 3, + pending: 2, + running: 1 + ) + end + + describe 'with large amount of pipelines' do + it 'sets the PIPELINES_COUNT_LIMIT constant to a value of 1_000' do + expect(described_class::PIPELINES_COUNT_LIMIT).to eq(1_000) + end + + context 'when there are more records than the limit' do + before do + stub_const('Gitlab::PipelineScopeCounts::PIPELINES_COUNT_LIMIT', 3) + end + + it 'limits the found items' do + expect(described_class.new(current_user, project, {}).all).to eq(3) + end + end + end +end diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index 891482a5f17..8211806a809 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -40,6 +40,17 @@ RSpec.describe Gitlab::Popen do it { expect(@output).to include('No such file or directory') } end + context 'non-zero status with a kill' do + let(:cmd) { [Gem.ruby, "-e", "thr = Thread.new { sleep 5 }; Process.kill(9, Process.pid); thr.join"] } + + before do + @output, @status = @klass.new.popen(cmd) + end + + it { expect(@status).to eq(9) } + it { expect(@output).to be_empty } + end + context 'unsafe string command' do it 'raises an error when it gets called with a string argument' do expect { @klass.new.popen('ls', path) }.to raise_error(RuntimeError) diff --git a/spec/lib/gitlab/process_memory_cache/helper_spec.rb b/spec/lib/gitlab/process_memory_cache/helper_spec.rb index 27d7fd0bdcf..bad4f61282c 100644 --- a/spec/lib/gitlab/process_memory_cache/helper_spec.rb +++ b/spec/lib/gitlab/process_memory_cache/helper_spec.rb @@ -33,13 +33,20 @@ RSpec.describe Gitlab::ProcessMemoryCache::Helper, :use_clean_rails_memory_store end it 'resets the cache when the shared key is missing', :aggregate_failures do - expect(Rails.cache).to receive(:read).with(:cached_content_instance_key).twice.and_return(nil) + allow(Rails.cache).to receive(:read).with(:cached_content_instance_key).and_return(nil) is_expected.to receive(:expensive_computation).thrice.and_return(1, 2, 3) 3.times do |index| expect(subject.cached_content).to eq(index + 1) end end + + it 'does not set the shared timestamp if it is already present', :redis do + subject.clear_cached_content + is_expected.to receive(:expensive_computation).once.and_return(1) + + expect { subject.cached_content }.not_to change { Rails.cache.read(:cached_content_instance_key) } + end end describe '.invalidate_memory_cache' do diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb index 16066934194..7852470196b 100644 --- a/spec/lib/gitlab/project_authorizations_spec.rb +++ b/spec/lib/gitlab/project_authorizations_spec.rb @@ -334,7 +334,7 @@ RSpec.describe Gitlab::ProjectAuthorizations do let(:common_id) { non_existing_record_id } let!(:group) { create(:group, id: common_id) } let!(:unrelated_project) { create(:project, id: common_id) } - let(:user) { unrelated_project.owner } + let(:user) { unrelated_project.first_owner } it 'does not have access to group and its projects' do mapping = map_access_levels(authorizations) @@ -345,4 +345,76 @@ RSpec.describe Gitlab::ProjectAuthorizations do end end end + + context 'with pending memberships' do + let_it_be(:group) { create(:group) } + let_it_be(:user) { create(:user) } + + subject(:mapping) { map_access_levels(authorizations) } + + context 'group membership' do + let!(:group_project) { create(:project, namespace: group) } + + before do + create(:group_member, :developer, :awaiting, user: user, group: group) + end + + it 'does not create authorization' do + expect(mapping[group_project.id]).to be_nil + end + end + + context 'inherited group membership' do + let!(:sub_group) { create(:group, parent: group) } + let!(:sub_group_project) { create(:project, namespace: sub_group) } + + before do + create(:group_member, :developer, :awaiting, user: user, group: group) + end + + it 'does not create authorization' do + expect(mapping[sub_group_project.id]).to be_nil + end + end + + context 'project membership' do + let!(:group_project) { create(:project, namespace: group) } + + before do + create(:project_member, :developer, :awaiting, user: user, project: group_project) + end + + it 'does not create authorization' do + expect(mapping[group_project.id]).to be_nil + end + end + + context 'shared group' do + let!(:shared_group) { create(:group) } + let!(:shared_group_project) { create(:project, namespace: shared_group) } + + before do + create(:group_group_link, shared_group: shared_group, shared_with_group: group) + create(:group_member, :developer, :awaiting, user: user, group: group) + end + + it 'does not create authorization' do + expect(mapping[shared_group_project.id]).to be_nil + end + end + + context 'shared project' do + let!(:another_group) { create(:group) } + let!(:shared_project) { create(:project, namespace: another_group) } + + before do + create(:project_group_link, group: group, project: shared_project) + create(:group_member, :developer, :awaiting, user: user, group: group) + end + + it 'does not create authorization' do + expect(mapping[shared_project.id]).to be_nil + end + end + end end diff --git a/spec/lib/gitlab/rack_attack/request_spec.rb b/spec/lib/gitlab/rack_attack/request_spec.rb index ecdcc23e588..b8a26a64e5b 100644 --- a/spec/lib/gitlab/rack_attack/request_spec.rb +++ b/spec/lib/gitlab/rack_attack/request_spec.rb @@ -5,6 +5,20 @@ require 'spec_helper' RSpec.describe Gitlab::RackAttack::Request do using RSpec::Parameterized::TableSyntax + let(:path) { '/' } + let(:env) { {} } + let(:session) { {} } + let(:request) do + ::Rack::Attack::Request.new( + env.reverse_merge( + 'REQUEST_METHOD' => 'GET', + 'PATH_INFO' => Gitlab.config.gitlab.relative_url_root + path, + 'rack.input' => StringIO.new, + 'rack.session' => session + ) + ) + end + describe 'FILES_PATH_REGEX' do subject { described_class::FILES_PATH_REGEX } @@ -16,11 +30,249 @@ RSpec.describe Gitlab::RackAttack::Request do it { is_expected.not_to match('/api/v4/projects/some/nested/repo/repository/files/README') } end + describe '#api_request?' do + subject { request.api_request? } + + where(:path, :expected) do + '/' | false + '/groups' | false + '/foo/api' | false + + '/api' | true + '/api/v4/groups/1' | true + end + + with_them do + it { is_expected.to eq(expected) } + + context 'when the application is mounted at a relative URL' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it { is_expected.to eq(expected) } + end + end + end + + describe '#api_internal_request?' do + subject { request.api_internal_request? } + + where(:path, :expected) do + '/' | false + '/groups' | false + '/api' | false + '/api/v4/groups/1' | false + '/api/v4/internal' | false + '/foo/api/v4/internal' | false + + '/api/v4/internal/' | true + '/api/v4/internal/foo' | true + '/api/v1/internal/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + + context 'when the application is mounted at a relative URL' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it { is_expected.to eq(expected) } + end + end + end + + describe '#health_check_request?' do + subject { request.health_check_request? } + + where(:path, :expected) do + '/' | false + '/groups' | false + '/foo/-/health' | false + + '/-/health' | true + '/-/liveness' | true + '/-/readiness' | true + '/-/metrics' | true + '/-/health/foo' | true + '/-/liveness/foo' | true + '/-/readiness/foo' | true + '/-/metrics/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + + context 'when the application is mounted at a relative URL' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it { is_expected.to eq(expected) } + end + end + end + + describe '#container_registry_event?' do + subject { request.container_registry_event? } + + where(:path, :expected) do + '/' | false + '/groups' | false + '/api/v4/container_registry_event' | false + '/foo/api/v4/container_registry_event/' | false + + '/api/v4/container_registry_event/' | true + '/api/v4/container_registry_event/foo' | true + '/api/v1/container_registry_event/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + + context 'when the application is mounted at a relative URL' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it { is_expected.to eq(expected) } + end + end + end + + describe '#product_analytics_collector_request?' do + subject { request.product_analytics_collector_request? } + + where(:path, :expected) do + '/' | false + '/groups' | false + '/-/collector' | false + '/-/collector/foo' | false + '/foo/-/collector/i' | false + + '/-/collector/i' | true + '/-/collector/ifoo' | true + '/-/collector/i/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + + context 'when the application is mounted at a relative URL' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it { is_expected.to eq(expected) } + end + end + end + + describe '#should_be_skipped?' do + where( + api_internal_request: [true, false], + health_check_request: [true, false], + container_registry_event: [true, false] + ) + + with_them do + it 'returns true if any condition is true' do + allow(request).to receive(:api_internal_request?).and_return(api_internal_request) + allow(request).to receive(:health_check_request?).and_return(health_check_request) + allow(request).to receive(:container_registry_event?).and_return(container_registry_event) + + expect(request.should_be_skipped?).to be(api_internal_request || health_check_request || container_registry_event) + end + end + end + + describe '#web_request?' do + subject { request.web_request? } + + where(:path, :expected) do + '/' | true + '/groups' | true + '/foo/api' | true + + '/api' | false + '/api/v4/groups/1' | false + end + + with_them do + it { is_expected.to eq(expected) } + + context 'when the application is mounted at a relative URL' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it { is_expected.to eq(expected) } + end + end + end + + describe '#protected_path?' do + subject { request.protected_path? } + + before do + stub_application_setting(protected_paths: [ + '/protected', + '/secure' + ]) + end + + where(:path, :expected) do + '/' | false + '/groups' | false + '/foo/protected' | false + '/foo/secure' | false + + '/protected' | true + '/secure' | true + '/secure/' | true + '/secure/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + + context 'when the application is mounted at a relative URL' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it { is_expected.to eq(expected) } + end + end + end + + describe '#frontend_request?', :allow_forgery_protection do + subject { request.send(:frontend_request?) } + + let(:path) { '/' } + + # Define these as local variables so we can use them in the `where` block. + valid_token = SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) + other_token = SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) + + where(:session, :env, :expected) do + {} | {} | false # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + {} | { 'HTTP_X_CSRF_TOKEN' => valid_token } | false + { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => other_token } | false + { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => valid_token } | true + end + + with_them do + it { is_expected.to eq(expected) } + end + end + describe '#deprecated_api_request?' do - let(:env) { { 'REQUEST_METHOD' => 'GET', 'rack.input' => StringIO.new, 'PATH_INFO' => path, 'QUERY_STRING' => query } } - let(:request) { ::Rack::Attack::Request.new(env) } + subject { request.send(:deprecated_api_request?) } - subject { !!request.__send__(:deprecated_api_request?) } + let(:env) { { 'QUERY_STRING' => query } } where(:path, :query, :expected) do '/' | '' | false @@ -42,6 +294,14 @@ RSpec.describe Gitlab::RackAttack::Request do with_them do it { is_expected.to eq(expected) } + + context 'when the application is mounted at a relative URL' do + before do + stub_config_setting(relative_url_root: '/gitlab/root') + end + + it { is_expected.to eq(expected) } + end end end end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 8d67350f0f3..54a0b282e99 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::Regex do shared_examples_for 'project/group name chars regex' do diff --git a/spec/lib/gitlab/request_profiler/profile_spec.rb b/spec/lib/gitlab/request_profiler/profile_spec.rb index 2e9c75dde87..30e23a99b22 100644 --- a/spec/lib/gitlab/request_profiler/profile_spec.rb +++ b/spec/lib/gitlab/request_profiler/profile_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::RequestProfiler::Profile do let(:profile) { described_class.new(filename) } diff --git a/spec/lib/gitlab/runtime_spec.rb b/spec/lib/gitlab/runtime_spec.rb index 4627a8db82e..402b72b9220 100644 --- a/spec/lib/gitlab/runtime_spec.rb +++ b/spec/lib/gitlab/runtime_spec.rb @@ -26,8 +26,16 @@ RSpec.describe Gitlab::Runtime do end context "when unknown" do - it "raises an exception when trying to identify" do - expect { subject.identify }.to raise_error(subject::UnknownProcessError) + describe '.identify' do + it "raises an exception when trying to identify" do + expect { subject.identify }.to raise_error(subject::UnknownProcessError) + end + end + + describe '.safe_identify' do + it "returns nil" do + expect(subject.safe_identify).to be_nil + end end end @@ -37,8 +45,16 @@ RSpec.describe Gitlab::Runtime do stub_const('::Rails::Console', double) end - it "raises an exception when trying to identify" do - expect { subject.identify }.to raise_error(subject::AmbiguousProcessError) + describe '.identify' do + it "raises an exception when trying to identify" do + expect { subject.identify }.to raise_error(subject::AmbiguousProcessError) + end + end + + describe '.safe_identify' do + it "returns nil" do + expect(subject.safe_identify).to be_nil + end end end diff --git a/spec/lib/gitlab/security/scan_configuration_spec.rb b/spec/lib/gitlab/security/scan_configuration_spec.rb index 0af029968e8..2e8a11dfda3 100644 --- a/spec/lib/gitlab/security/scan_configuration_spec.rb +++ b/spec/lib/gitlab/security/scan_configuration_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe ::Gitlab::Security::ScanConfiguration do + using RSpec::Parameterized::TableSyntax + let_it_be(:project) { create(:project, :repository) } let(:scan) { described_class.new(project: project, type: type, configured: configured) } @@ -13,9 +15,11 @@ RSpec.describe ::Gitlab::Security::ScanConfiguration do let(:configured) { true } context 'with a core scanner' do - let(:type) { :sast } + where(type: %i(sast sast_iac secret_detection)) - it { is_expected.to be_truthy } + with_them do + it { is_expected.to be_truthy } + end end context 'with custom scanner' do @@ -38,27 +42,28 @@ RSpec.describe ::Gitlab::Security::ScanConfiguration do subject { scan.configuration_path } let(:configured) { true } + let(:type) { :sast } - context 'with a non configurable scanner' do - let(:type) { :secret_detection } + it { is_expected.to be_nil } + end - it { is_expected.to be_nil } - end + describe '#can_enable_by_merge_request?' do + subject { scan.can_enable_by_merge_request? } - context 'with licensed scanner for FOSS environment' do - let(:type) { :dast } + let(:configured) { true } - before do - stub_env('FOSS_ONLY', '1') - end + context 'with a core scanner' do + where(type: %i(sast sast_iac secret_detection)) - it { is_expected.to be_nil } + with_them do + it { is_expected.to be_truthy } + end end - context 'with custom scanner' do + context 'with a custom scanner' do let(:type) { :my_scanner } - it { is_expected.to be_nil } + it { is_expected.to be_falsey } end end end diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb index 38486b313cb..cf5d2c3b455 100644 --- a/spec/lib/gitlab/ssh_public_key_spec.rb +++ b/spec/lib/gitlab/ssh_public_key_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do end where(:name) do - [:rsa, :dsa, :ecdsa, :ed25519] + [:rsa, :dsa, :ecdsa, :ed25519, :ecdsa_sk, :ed25519_sk] end with_them do @@ -24,7 +24,7 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do describe '.supported_types' do it 'returns array with the names of supported technologies' do expect(described_class.supported_types).to eq( - [:rsa, :dsa, :ecdsa, :ed25519] + [:rsa, :dsa, :ecdsa, :ed25519, :ecdsa_sk, :ed25519_sk] ) end end @@ -35,7 +35,9 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do [:rsa, [1024, 2048, 3072, 4096]], [:dsa, [1024, 2048, 3072]], [:ecdsa, [256, 384, 521]], - [:ed25519, [256]] + [:ed25519, [256]], + [:ecdsa_sk, [256]], + [:ed25519_sk, [256]] ] end @@ -53,6 +55,8 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 + sk-ecdsa-sha2-nistp256@openssh.com + sk-ssh-ed25519@openssh.com ) ) end @@ -64,7 +68,9 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do [:rsa, %w(ssh-rsa)], [:dsa, %w(ssh-dss)], [:ecdsa, %w(ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521)], - [:ed25519, %w(ssh-ed25519)] + [:ed25519, %w(ssh-ed25519)], + [:ecdsa_sk, %w(sk-ecdsa-sha2-nistp256@openssh.com)], + [:ed25519_sk, %w(sk-ssh-ed25519@openssh.com)] ] end @@ -122,13 +128,35 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do rsa_key_8192 dsa_key_2048 ecdsa_key_256 - ed25519_key_256) + ed25519_key_256 + ecdsa_sk_key_256 + ed25519_sk_key_256) end with_them do let(:key) { attributes_for(factory)[:key] } it { is_expected.to be_valid } + + context 'when key begins with options' do + let(:key) { "restrict,command='dump /home' #{attributes_for(factory)[:key]}" } + + it { is_expected.to be_valid } + end + + context 'when key is in known_hosts format' do + context "when key begins with 'example.com'" do + let(:key) { "example.com #{attributes_for(factory)[:key]}" } + + it { is_expected.to be_valid } + end + + context "when key begins with '@revoked other.example.com'" do + let(:key) { "@revoked other.example.com #{attributes_for(factory)[:key]}" } + + it { is_expected.to be_valid } + end + end end end @@ -137,6 +165,40 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do it { is_expected.not_to be_valid } end + + context 'when an unsupported SSH key algorithm' do + let(:key) { "unsupported-#{attributes_for(:rsa_key_2048)[:key]}" } + + it { is_expected.not_to be_valid } + end + end + + shared_examples 'raises error when the key is represented by a class that is not in the list of supported technologies' do + context 'when the key is represented by a class that is not in the list of supported technologies' do + it 'raises error' do + klass = Class.new + key = klass.new + + allow(public_key).to receive(:key).and_return(key) + + expect { subject }.to raise_error("Unsupported key type: #{key.class}") + end + end + + context 'when the key is represented by a subclass of the class that is in the list of supported technologies' do + it 'raises error' do + rsa_subclass = Class.new(described_class.technology(:rsa).key_class) do + def initialize + end + end + + key = rsa_subclass.new + + allow(public_key).to receive(:key).and_return(key) + + expect { subject }.to raise_error("Unsupported key type: #{key.class}") + end + end end describe '#type' do @@ -147,7 +209,9 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do [:rsa_key_2048, :rsa], [:dsa_key_2048, :dsa], [:ecdsa_key_256, :ecdsa], - [:ed25519_key_256, :ed25519] + [:ed25519_key_256, :ed25519], + [:ecdsa_sk_key_256, :ecdsa_sk], + [:ed25519_sk_key_256, :ed25519_sk] ] end @@ -162,6 +226,8 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do it { is_expected.to be_nil } end + + include_examples 'raises error when the key is represented by a class that is not in the list of supported technologies' end describe '#bits' do @@ -175,7 +241,9 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do [:rsa_key_8192, 8192], [:dsa_key_2048, 2048], [:ecdsa_key_256, 256], - [:ed25519_key_256, 256] + [:ed25519_key_256, 256], + [:ecdsa_sk_key_256, 256], + [:ed25519_sk_key_256, 256] ] end @@ -190,6 +258,8 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do it { is_expected.to be_nil } end + + include_examples 'raises error when the key is represented by a class that is not in the list of supported technologies' end describe '#fingerprint' do @@ -203,7 +273,9 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do [:rsa_key_8192, 'fb:53:7f:e9:2f:f7:17:aa:c8:32:52:06:8e:05:e2:82'], [:dsa_key_2048, 'c8:85:1e:df:44:0f:20:00:3c:66:57:2b:21:10:5a:27'], [:ecdsa_key_256, '67:a3:a9:7d:b8:e1:15:d4:80:40:21:34:bb:ed:97:38'], - [:ed25519_key_256, 'e6:eb:45:8a:3c:59:35:5f:e9:5b:80:12:be:7e:22:73'] + [:ed25519_key_256, 'e6:eb:45:8a:3c:59:35:5f:e9:5b:80:12:be:7e:22:73'], + [:ecdsa_sk_key_256, '56:b9:bc:99:3d:2f:cf:63:6b:70:d8:f9:40:7e:09:4c'], + [:ed25519_sk_key_256, 'f9:a0:64:0b:4b:72:72:0e:62:92:d7:04:14:74:1c:c9'] ] end @@ -220,18 +292,20 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do end end - describe '#fingerprint in SHA256 format' do - subject { public_key.fingerprint("SHA256").gsub("SHA256:", "") if public_key.fingerprint("SHA256") } + describe '#fingerprint_sha256' do + subject { public_key.fingerprint_sha256 } where(:factory, :fingerprint_sha256) do [ - [:rsa_key_2048, 'GdtgO0eHbwLB+mK47zblkoXujkqKRZjgMQrHH6Kks3E'], - [:rsa_key_4096, 'ByDU7hQ1JB95l6p53rHrffc4eXvEtqGUtQhS+Dhyy7g'], - [:rsa_key_5120, 'PCCupLbFHScm4AbEufbGDvhBU27IM0MVAor715qKQK8'], - [:rsa_key_8192, 'CtHFQAS+9Hb8z4vrv4gVQPsHjNN0WIZhWODaB1mQLs4'], - [:dsa_key_2048, '+a3DQ7cU5GM+gaYOfmc0VWNnykHQSuth3VRcCpWuYNI'], - [:ecdsa_key_256, 'C+I5k3D+IGeM6k5iBR1ZsphqTKV+7uvL/XZ5hcrTr7g'], - [:ed25519_key_256, 'DCKAjzxWrdOTjaGKBBjtCW8qY5++GaiAJflrHPmp6W0'] + [:rsa_key_2048, 'SHA256:GdtgO0eHbwLB+mK47zblkoXujkqKRZjgMQrHH6Kks3E'], + [:rsa_key_4096, 'SHA256:ByDU7hQ1JB95l6p53rHrffc4eXvEtqGUtQhS+Dhyy7g'], + [:rsa_key_5120, 'SHA256:PCCupLbFHScm4AbEufbGDvhBU27IM0MVAor715qKQK8'], + [:rsa_key_8192, 'SHA256:CtHFQAS+9Hb8z4vrv4gVQPsHjNN0WIZhWODaB1mQLs4'], + [:dsa_key_2048, 'SHA256:+a3DQ7cU5GM+gaYOfmc0VWNnykHQSuth3VRcCpWuYNI'], + [:ecdsa_key_256, 'SHA256:C+I5k3D+IGeM6k5iBR1ZsphqTKV+7uvL/XZ5hcrTr7g'], + [:ed25519_key_256, 'SHA256:DCKAjzxWrdOTjaGKBBjtCW8qY5++GaiAJflrHPmp6W0'], + [:ecdsa_sk_key_256, 'SHA256:N0sNKBgWKK8usPuPegtgzHQQA9vQ/dRhAEhwFDAnLA4'], + [:ed25519_sk_key_256, 'SHA256:U8IKRkIHed6vFMTflwweA3HhIf2DWgZ8EFTm9fgwOUk'] ] end @@ -249,10 +323,19 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do end describe '#key_text' do - let(:key) { 'this is not a key' } + where(:key_value) do + [ + 'this is not a key', + nil + ] + end - it 'carries the unmodified key data' do - expect(public_key.key_text).to eq(key) + with_them do + let(:key) { key_value } + + it 'carries the unmodified key data' do + expect(public_key.key_text).to eq(key) + end end end end diff --git a/spec/lib/gitlab/subscription_portal_spec.rb b/spec/lib/gitlab/subscription_portal_spec.rb index 627d3bb42c7..fd3654afee0 100644 --- a/spec/lib/gitlab/subscription_portal_spec.rb +++ b/spec/lib/gitlab/subscription_portal_spec.rb @@ -61,7 +61,6 @@ RSpec.describe ::Gitlab::SubscriptionPortal do :subscriptions_more_minutes_url | 'https://customers.staging.gitlab.com/buy_pipeline_minutes' :subscriptions_more_storage_url | 'https://customers.staging.gitlab.com/buy_storage' :subscriptions_manage_url | 'https://customers.staging.gitlab.com/subscriptions' - :subscriptions_plans_url | 'https://about.gitlab.com/pricing/' :subscriptions_instance_review_url | 'https://customers.staging.gitlab.com/instance_review' :subscriptions_gitlab_plans_url | 'https://customers.staging.gitlab.com/gitlab_plans' :edit_account_url | 'https://customers.staging.gitlab.com/customers/edit' diff --git a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb index 42fc84cf076..b021abc9f25 100644 --- a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb +++ b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'support/shared_examples/lib/gitlab/malicious_regexp_shared_examples' -require 'support/helpers/stub_feature_flags' +require 'spec_helper' RSpec.describe Gitlab::UntrustedRegexp::RubySyntax do describe '.matches_syntax?' do @@ -77,6 +75,7 @@ RSpec.describe Gitlab::UntrustedRegexp::RubySyntax do include StubFeatureFlags before do + # When removed we could use `require 'fast_spec_helper'` again. stub_feature_flags(allow_unsafe_ruby_regexp: true) allow(Gitlab::UntrustedRegexp).to receive(:new).and_raise(RegexpError) diff --git a/spec/lib/gitlab/usage/service_ping_report_spec.rb b/spec/lib/gitlab/usage/service_ping_report_spec.rb new file mode 100644 index 00000000000..9b9b24ad128 --- /dev/null +++ b/spec/lib/gitlab/usage/service_ping_report_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Usage::ServicePingReport, :use_clean_rails_memory_store_caching do + let(:usage_data) { { uuid: "1111" } } + + context 'for output: :all_metrics_values' do + it 'generates the service ping' do + expect(Gitlab::UsageData).to receive(:data) + + described_class.for(output: :all_metrics_values) + end + end + + context 'for output: :metrics_queries' do + it 'generates the service ping' do + expect(Gitlab::UsageDataQueries).to receive(:data) + + described_class.for(output: :metrics_queries) + end + end + + context 'for output: :non_sql_metrics_values' do + it 'generates the service ping' do + expect(Gitlab::UsageDataNonSqlMetrics).to receive(:data) + + described_class.for(output: :non_sql_metrics_values) + end + end + + context 'when using cached' do + context 'for cached: true' do + let(:new_usage_data) { { uuid: "1112" } } + + it 'caches the values' do + allow(Gitlab::UsageData).to receive(:data).and_return(usage_data, new_usage_data) + + expect(described_class.for(output: :all_metrics_values)).to eq(usage_data) + expect(described_class.for(output: :all_metrics_values, cached: true)).to eq(usage_data) + + expect(Rails.cache.fetch('usage_data')).to eq(usage_data) + end + + it 'writes to cache and returns fresh data' do + allow(Gitlab::UsageData).to receive(:data).and_return(usage_data, new_usage_data) + + expect(described_class.for(output: :all_metrics_values)).to eq(usage_data) + expect(described_class.for(output: :all_metrics_values)).to eq(new_usage_data) + expect(described_class.for(output: :all_metrics_values, cached: true)).to eq(new_usage_data) + + expect(Rails.cache.fetch('usage_data')).to eq(new_usage_data) + end + end + + context 'when no caching' do + let(:new_usage_data) { { uuid: "1112" } } + + it 'returns fresh data' do + allow(Gitlab::UsageData).to receive(:data).and_return(usage_data, new_usage_data) + + expect(described_class.for(output: :all_metrics_values)).to eq(usage_data) + expect(described_class.for(output: :all_metrics_values)).to eq(new_usage_data) + + expect(Rails.cache.fetch('usage_data')).to eq(new_usage_data) + end + end + end +end diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index f7ff68af8a2..5e74ea3293c 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -49,7 +49,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s 'secure', 'importer', 'network_policies', - 'geo' + 'geo', + 'growth' ) end end diff --git a/spec/lib/gitlab/usage_data_counters/jetbrains_plugin_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/jetbrains_plugin_activity_unique_counter_spec.rb new file mode 100644 index 00000000000..4169546edad --- /dev/null +++ b/spec/lib/gitlab/usage_data_counters/jetbrains_plugin_activity_unique_counter_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::UsageDataCounters::JetBrainsPluginActivityUniqueCounter, :clean_gitlab_redis_shared_state do # rubocop:disable RSpec/FilePath + let(:user1) { build(:user, id: 1) } + let(:user2) { build(:user, id: 2) } + let(:time) { Time.current } + let(:action) { described_class::JETBRAINS_API_REQUEST_ACTION } + let(:user_agent) { { user_agent: 'gitlab-jetbrains-plugin/0.0.1 intellij-idea/2021.2.4 java/11.0.13 mac-os-x/aarch64/12.1' } } + + context 'when tracking a jetbrains api request' do + it_behaves_like 'a request from an extension' + end +end diff --git a/spec/lib/gitlab/usage_data_counters/vscode_extenion_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/vscode_extenion_activity_unique_counter_spec.rb deleted file mode 100644 index 7593d51fe76..00000000000 --- a/spec/lib/gitlab/usage_data_counters/vscode_extenion_activity_unique_counter_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.shared_examples 'a tracked vs code unique action' do |event| - before do - stub_application_setting(usage_ping_enabled: true) - end - - def count_unique(date_from:, date_to:) - Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to) - end - - it 'tracks when the user agent is from vs code' do - aggregate_failures do - user_agent = { user_agent: 'vs-code-gitlab-workflow/3.11.1 VSCode/1.52.1 Node.js/12.14.1 (darwin; x64)' } - - expect(track_action(user: user1, **user_agent)).to be_truthy - expect(track_action(user: user1, **user_agent)).to be_truthy - expect(track_action(user: user2, **user_agent)).to be_truthy - - expect(count_unique(date_from: time - 1.week, date_to: time + 1.week)).to eq(2) - end - end - - it 'does not track when the user agent is not from vs code' do - aggregate_failures do - user_agent = { user_agent: 'normal_user_agent' } - - expect(track_action(user: user1, **user_agent)).to be_falsey - expect(track_action(user: user1, **user_agent)).to be_falsey - expect(track_action(user: user2, **user_agent)).to be_falsey - - expect(count_unique(date_from: time - 1.week, date_to: time + 1.week)).to eq(0) - end - end - - it 'does not track if user agent is not present' do - expect(track_action(user: nil, user_agent: nil)).to be_nil - end - - it 'does not track if user is not present' do - user_agent = { user_agent: 'vs-code-gitlab-workflow/3.11.1 VSCode/1.52.1 Node.js/12.14.1 (darwin; x64)' } - - expect(track_action(user: nil, **user_agent)).to be_nil - end -end - -RSpec.describe Gitlab::UsageDataCounters::VSCodeExtensionActivityUniqueCounter, :clean_gitlab_redis_shared_state do - let(:user1) { build(:user, id: 1) } - let(:user2) { build(:user, id: 2) } - let(:time) { Time.current } - - context 'when tracking a vs code api request' do - it_behaves_like 'a tracked vs code unique action' do - let(:action) { described_class::VS_CODE_API_REQUEST_ACTION } - - def track_action(params) - described_class.track_api_request_when_trackable(**params) - end - end - end -end diff --git a/spec/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter_spec.rb new file mode 100644 index 00000000000..640dadd8c0b --- /dev/null +++ b/spec/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::UsageDataCounters::VSCodeExtensionActivityUniqueCounter, :clean_gitlab_redis_shared_state do # rubocop:disable RSpec/FilePath + let(:user1) { build(:user, id: 1) } + let(:user2) { build(:user, id: 2) } + let(:time) { Time.current } + let(:action) { described_class::VS_CODE_API_REQUEST_ACTION } + let(:user_agent) { { user_agent: 'vs-code-gitlab-workflow/3.11.1 VSCode/1.52.1 Node.js/12.14.1 (darwin; x64)' } } + + context 'when tracking a vs code api request' do + it_behaves_like 'a request from an extension' + end +end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 427e8e67090..bea07dd9c43 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -12,8 +12,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do stub_database_flavor_check('Cloud SQL for PostgreSQL') end - describe '.uncached_data' do - subject { described_class.uncached_data } + describe '.data' do + subject { described_class.data } it 'includes basic top and second level keys' do is_expected.to include(:counts) @@ -556,8 +556,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do expect(count_data[:issues_created_from_alerts]).to eq(3) expect(count_data[:issues_created_manually_from_alerts]).to eq(1) expect(count_data[:alert_bot_incident_issues]).to eq(4) - expect(count_data[:incident_labeled_issues]).to eq(3) - expect(count_data[:clusters_enabled]).to eq(6) expect(count_data[:project_clusters_enabled]).to eq(4) expect(count_data[:group_clusters_enabled]).to eq(1) diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb index d756ec5ef83..ba6997adbf6 100644 --- a/spec/lib/gitlab/utils_spec.rb +++ b/spec/lib/gitlab/utils_spec.rb @@ -439,6 +439,23 @@ RSpec.describe Gitlab::Utils do end end + describe '.add_url_parameters' do + subject { described_class.add_url_parameters(url, params) } + + where(:url, :params, :expected_url) do + nil | nil | '' + nil | { b: 3, a: 2 } | '?a=2&b=3' + 'https://gitlab.com' | nil | 'https://gitlab.com' + 'https://gitlab.com' | { b: 3, a: 2 } | 'https://gitlab.com?a=2&b=3' + 'https://gitlab.com?a=1#foo' | { b: 3, 'a': 2 } | 'https://gitlab.com?a=2&b=3#foo' + 'https://gitlab.com?a=1#foo' | [[:b, 3], [:a, 2]] | 'https://gitlab.com?a=2&b=3#foo' + end + + with_them do + it { is_expected.to eq(expected_url) } + end + end + describe '.removes_sensitive_data_from_url' do it 'returns string object' do expect(described_class.removes_sensitive_data_from_url('http://gitlab.com')).to be_instance_of(String) diff --git a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb index 9af21685c9e..66c9bb00ee9 100644 --- a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb +++ b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb @@ -108,7 +108,7 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Global do describe '#errors' do it 'reports errors about missing script' do expect(global.errors) - .to include "terminal:before_script config should be an array containing strings and arrays of strings" + .to include "terminal:before_script config should be a string or a nested array of strings up to 10 levels deep" end end end diff --git a/spec/lib/gitlab/web_ide/config_spec.rb b/spec/lib/gitlab/web_ide/config_spec.rb index 7a9011d03c0..7ee9d40410c 100644 --- a/spec/lib/gitlab/web_ide/config_spec.rb +++ b/spec/lib/gitlab/web_ide/config_spec.rb @@ -56,7 +56,7 @@ RSpec.describe Gitlab::WebIde::Config do end context 'when config logic is incorrect' do - let(:yml) { 'terminal: { before_script: "ls" }' } + let(:yml) { 'terminal: { before_script: 123 }' } describe '#valid?' do it 'is not valid' do diff --git a/spec/lib/gitlab/webpack/file_loader_spec.rb b/spec/lib/gitlab/webpack/file_loader_spec.rb index 34d00b9f106..6475ef58611 100644 --- a/spec/lib/gitlab/webpack/file_loader_spec.rb +++ b/spec/lib/gitlab/webpack/file_loader_spec.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'support/helpers/file_read_helpers' -require 'support/webmock' +require 'spec_helper' RSpec.describe Gitlab::Webpack::FileLoader do include FileReadHelpers diff --git a/spec/lib/gitlab_edition_spec.rb b/spec/lib/gitlab_edition_spec.rb index 2f1316819ec..6fc4312252d 100644 --- a/spec/lib/gitlab_edition_spec.rb +++ b/spec/lib/gitlab_edition_spec.rb @@ -3,18 +3,22 @@ require 'spec_helper' RSpec.describe GitlabEdition do + def remove_instance_variable(ivar) + described_class.remove_instance_variable(ivar) if described_class.instance_variable_defined?(ivar) + end + before do # Make sure the ENV is clean stub_env('FOSS_ONLY', nil) stub_env('EE_ONLY', nil) - described_class.instance_variable_set(:@is_ee, nil) - described_class.instance_variable_set(:@is_jh, nil) + remove_instance_variable(:@is_ee) + remove_instance_variable(:@is_jh) end after do - described_class.instance_variable_set(:@is_ee, nil) - described_class.instance_variable_set(:@is_jh, nil) + remove_instance_variable(:@is_ee) + remove_instance_variable(:@is_jh) end describe '.root' do @@ -51,7 +55,7 @@ RSpec.describe GitlabEdition do allow(described_class).to receive(:ee?).and_return(false) end - it 'returns the exyensions according to the current edition' do + it 'returns the extensions according to the current edition' do expect(described_class.extensions).to be_empty end end @@ -77,7 +81,7 @@ RSpec.describe GitlabEdition do end describe '.ee?' do - context 'for EE' do + context 'when EE' do before do stub_path('ee/app/models/license.rb', exist?: true) end @@ -109,7 +113,7 @@ RSpec.describe GitlabEdition do end end - context 'for CE' do + context 'when CE' do before do stub_path('ee/app/models/license.rb', exist?: false) end @@ -121,12 +125,9 @@ RSpec.describe GitlabEdition do end describe '.jh?' do - context 'for JH' do + context 'when JH' do before do - stub_path( - 'ee/app/models/license.rb', - 'jh', - exist?: true) + stub_path('ee/app/models/license.rb', 'jh', exist?: true) end context 'when using default FOSS_ONLY and EE_ONLY' do diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index 49ba4debe31..57a4bdc9bb5 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -99,6 +99,13 @@ RSpec.describe Gitlab do expect(described_class.com?).to eq true end + it 'is true when on other gitlab subdomain with hyphen' do + url_with_subdomain = Gitlab::Saas.com_url.gsub('https://', 'https://test-example.') + stub_config_setting(url: url_with_subdomain) + + expect(described_class.com?).to eq true + end + it 'is false when not on GitLab.com' do stub_config_setting(url: 'http://example.com') diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index 3284c9cd0d1..29e5445cfaa 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -6,6 +6,8 @@ RSpec.describe GoogleApi::CloudPlatform::Client do let(:token) { 'token' } let(:client) { described_class.new(token, nil) } let(:user_agent_options) { client.instance_eval { user_agent_header } } + let(:gcp_project_id) { String('gcp_proj_id') } + let(:operation) { true } describe '.session_key_for_redirect_uri' do let(:state) { 'random_string' } @@ -60,7 +62,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do before do allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) .to receive(:get_zone_cluster).with(any_args, options: user_agent_options) - .and_return(gke_cluster) + .and_return(gke_cluster) end it { is_expected.to eq(gke_cluster) } @@ -122,7 +124,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do before do allow_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService) .to receive(:create_cluster).with(any_args) - .and_return(operation) + .and_return(operation) end it 'sets corresponded parameters' do @@ -172,7 +174,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do before do allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) .to receive(:get_zone_operation).with(any_args, options: user_agent_options) - .and_return(operation) + .and_return(operation) end it { is_expected.to eq(operation) } @@ -244,7 +246,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do let(:operation) { double('Service Account Key') } - it 'class Google Api IamService#create_service_account_key' do + it 'calls Google Api IamService#create_service_account_key' do expect_any_instance_of(Google::Apis::IamV1::IamService) .to receive(:create_service_account_key) .with(any_args) @@ -252,4 +254,84 @@ RSpec.describe GoogleApi::CloudPlatform::Client do is_expected.to eq(operation) end end + + describe 'grant_service_account_roles' do + subject { client.grant_service_account_roles(spy, spy) } + + it 'calls Google Api CloudResourceManager#set_iam_policy' do + mock_gcp_id = 'mock-gcp-id' + mock_email = 'mock@email.com' + mock_policy = Struct.new(:bindings).new([]) + mock_body = [] + + expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new) + .with({ 'role': 'roles/iam.serviceAccountUser', 'members': ["serviceAccount:#{mock_email}"] }) + + expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new) + .with({ 'role': 'roles/artifactregistry.admin', 'members': ["serviceAccount:#{mock_email}"] }) + + expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new) + .with({ 'role': 'roles/cloudbuild.builds.builder', 'members': ["serviceAccount:#{mock_email}"] }) + + expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new) + .with({ 'role': 'roles/run.admin', 'members': ["serviceAccount:#{mock_email}"] }) + + expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new) + .with({ 'role': 'roles/storage.admin', 'members': ["serviceAccount:#{mock_email}"] }) + + expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new) + .with({ 'role': 'roles/cloudsql.admin', 'members': ["serviceAccount:#{mock_email}"] }) + + expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new) + .with({ 'role': 'roles/browser', 'members': ["serviceAccount:#{mock_email}"] }) + + expect(Google::Apis::CloudresourcemanagerV1::SetIamPolicyRequest).to receive(:new).and_return([]) + + expect_next_instance_of(Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService) do |instance| + expect(instance).to receive(:get_project_iam_policy) + .with(mock_gcp_id) + .and_return(mock_policy) + expect(instance).to receive(:set_project_iam_policy) + .with(mock_gcp_id, mock_body) + end + + client.grant_service_account_roles(mock_gcp_id, mock_email) + end + end + + describe '#enable_cloud_run' do + subject { client.enable_cloud_run(gcp_project_id) } + + it 'calls Google Api IamService#create_service_account_key' do + expect_any_instance_of(Google::Apis::ServiceusageV1::ServiceUsageService) + .to receive(:enable_service) + .with("projects/#{gcp_project_id}/services/run.googleapis.com") + .and_return(operation) + is_expected.to eq(operation) + end + end + + describe '#enable_artifacts_registry' do + subject { client.enable_artifacts_registry(gcp_project_id) } + + it 'calls Google Api IamService#create_service_account_key' do + expect_any_instance_of(Google::Apis::ServiceusageV1::ServiceUsageService) + .to receive(:enable_service) + .with("projects/#{gcp_project_id}/services/artifactregistry.googleapis.com") + .and_return(operation) + is_expected.to eq(operation) + end + end + + describe '#enable_cloud_build' do + subject { client.enable_cloud_build(gcp_project_id) } + + it 'calls Google Api IamService#create_service_account_key' do + expect_any_instance_of(Google::Apis::ServiceusageV1::ServiceUsageService) + .to receive(:enable_service) + .with("projects/#{gcp_project_id}/services/cloudbuild.googleapis.com") + .and_return(operation) + is_expected.to eq(operation) + end + end end diff --git a/spec/lib/learn_gitlab/project_spec.rb b/spec/lib/learn_gitlab/project_spec.rb index 523703761bf..5d649740c65 100644 --- a/spec/lib/learn_gitlab/project_spec.rb +++ b/spec/lib/learn_gitlab/project_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe LearnGitlab::Project do let_it_be(:current_user) { create(:user) } let_it_be(:learn_gitlab_project) { create(:project, name: LearnGitlab::Project::PROJECT_NAME) } + let_it_be(:learn_gitlab_ultimate_trial_project) { create(:project, name: LearnGitlab::Project::PROJECT_NAME_ULTIMATE_TRIAL) } let_it_be(:learn_gitlab_board) { create(:board, project: learn_gitlab_project, name: LearnGitlab::Project::BOARD_NAME) } let_it_be(:learn_gitlab_label) { create(:label, project: learn_gitlab_project, name: LearnGitlab::Project::LABEL_NAME) } @@ -45,6 +46,12 @@ RSpec.describe LearnGitlab::Project do subject { described_class.new(current_user).project } it { is_expected.to eq learn_gitlab_project } + + context 'when it is created during trial signup' do + let_it_be(:learn_gitlab_project) { create(:project, name: LearnGitlab::Project::PROJECT_NAME_ULTIMATE_TRIAL) } + + it { is_expected.to eq learn_gitlab_project } + end end describe '.board' do diff --git a/spec/lib/peek/views/detailed_view_spec.rb b/spec/lib/peek/views/detailed_view_spec.rb index 8d6d9a829ef..149685b243a 100644 --- a/spec/lib/peek/views/detailed_view_spec.rb +++ b/spec/lib/peek/views/detailed_view_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Peek::Views::DetailedView, :request_store do context 'when a class defines thresholds' do diff --git a/spec/lib/security/ci_configuration/container_scanning_build_action_spec.rb b/spec/lib/security/ci_configuration/container_scanning_build_action_spec.rb new file mode 100644 index 00000000000..38066e41c53 --- /dev/null +++ b/spec/lib/security/ci_configuration/container_scanning_build_action_spec.rb @@ -0,0 +1,191 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Security::CiConfiguration::ContainerScanningBuildAction do + subject(:result) { described_class.new(auto_devops_enabled, gitlab_ci_content).generate } + + let(:params) { {} } + + context 'with existing .gitlab-ci.yml' do + let(:auto_devops_enabled) { false } + + context 'container_scanning has not been included' do + let(:expected_yml) do + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings + # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence + + # container_scanning: + # variables: + # DOCKER_IMAGE: ... + # DOCKER_USER: ... + # DOCKER_PASSWORD: ... + stages: + - test + - security + variables: + RANDOM: make sure this persists + include: + - template: existing.yml + - template: Security/Container-Scanning.gitlab-ci.yml + CI_YML + end + + context 'template includes are an array' do + let(:gitlab_ci_content) do + { "stages" => %w(test security), + "variables" => { "RANDOM" => "make sure this persists" }, + "include" => [{ "template" => "existing.yml" }] } + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(expected_yml) + end + end + + context 'template include is not an array' do + let(:gitlab_ci_content) do + { "stages" => %w(test security), + "variables" => { "RANDOM" => "make sure this persists" }, + "include" => { "template" => "existing.yml" } } + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(expected_yml) + end + end + end + + context 'container_scanning has been included' do + let(:expected_yml) do + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings + # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence + + # container_scanning: + # variables: + # DOCKER_IMAGE: ... + # DOCKER_USER: ... + # DOCKER_PASSWORD: ... + stages: + - test + variables: + RANDOM: make sure this persists + include: + - template: Security/Container-Scanning.gitlab-ci.yml + CI_YML + end + + context 'container_scanning template include are an array' do + let(:gitlab_ci_content) do + { "stages" => %w(test), + "variables" => { "RANDOM" => "make sure this persists" }, + "include" => [{ "template" => "Security/Container-Scanning.gitlab-ci.yml" }] } + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(expected_yml) + end + end + + context 'container_scanning template include is not an array' do + let(:gitlab_ci_content) do + { "stages" => %w(test), + "variables" => { "RANDOM" => "make sure this persists" }, + "include" => { "template" => "Security/Container-Scanning.gitlab-ci.yml" } } + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('update') + expect(result[:content]).to eq(expected_yml) + end + end + end + end + + context 'with no .gitlab-ci.yml' do + let(:gitlab_ci_content) { nil } + + context 'autodevops disabled' do + let(:auto_devops_enabled) { false } + let(:expected_yml) do + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings + # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence + + # container_scanning: + # variables: + # DOCKER_IMAGE: ... + # DOCKER_USER: ... + # DOCKER_PASSWORD: ... + include: + - template: Security/Container-Scanning.gitlab-ci.yml + CI_YML + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('create') + expect(result[:content]).to eq(expected_yml) + end + end + + context 'with autodevops enabled' do + let(:auto_devops_enabled) { true } + let(:expected_yml) do + <<-CI_YML.strip_heredoc + # You can override the included template(s) by including variable overrides + # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings + # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings + # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings + # Note that environment variables can be set in several places + # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence + + # container_scanning: + # variables: + # DOCKER_IMAGE: ... + # DOCKER_USER: ... + # DOCKER_PASSWORD: ... + include: + - template: Auto-DevOps.gitlab-ci.yml + CI_YML + end + + before do + allow_next_instance_of(described_class) do |secret_detection_build_actions| + allow(secret_detection_build_actions).to receive(:auto_devops_stages).and_return(fast_auto_devops_stages) + end + end + + it 'generates the correct YML' do + expect(result[:action]).to eq('create') + expect(result[:content]).to eq(expected_yml) + end + end + end + + # stubbing this method allows this spec file to use fast_spec_helper + def fast_auto_devops_stages + auto_devops_template = YAML.safe_load( File.read('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') ) + auto_devops_template['stages'] + end +end diff --git a/spec/lib/security/ci_configuration/sast_build_action_spec.rb b/spec/lib/security/ci_configuration/sast_build_action_spec.rb index d93175249f5..6f702e51b73 100644 --- a/spec/lib/security/ci_configuration/sast_build_action_spec.rb +++ b/spec/lib/security/ci_configuration/sast_build_action_spec.rb @@ -324,6 +324,7 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -344,6 +345,7 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -361,6 +363,7 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -384,6 +387,7 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -420,6 +424,7 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -445,6 +450,7 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -468,6 +474,7 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -492,6 +499,7 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -516,6 +524,7 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: diff --git a/spec/lib/security/ci_configuration/sast_iac_build_action_spec.rb b/spec/lib/security/ci_configuration/sast_iac_build_action_spec.rb index ecd1602dd9e..4c459058368 100644 --- a/spec/lib/security/ci_configuration/sast_iac_build_action_spec.rb +++ b/spec/lib/security/ci_configuration/sast_iac_build_action_spec.rb @@ -17,6 +17,7 @@ RSpec.describe Security::CiConfiguration::SastIacBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -64,6 +65,7 @@ RSpec.describe Security::CiConfiguration::SastIacBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -114,6 +116,7 @@ RSpec.describe Security::CiConfiguration::SastIacBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence include: @@ -135,6 +138,7 @@ RSpec.describe Security::CiConfiguration::SastIacBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence include: diff --git a/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb b/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb index 146c60ffb6e..4d9860ca4a5 100644 --- a/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb +++ b/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb @@ -17,6 +17,7 @@ RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -64,6 +65,7 @@ RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence stages: @@ -114,6 +116,7 @@ RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence include: @@ -135,6 +138,7 @@ RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings + # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence include: diff --git a/spec/lib/serializers/json_spec.rb b/spec/lib/serializers/json_spec.rb index 0c1801b34f9..96a57cde056 100644 --- a/spec/lib/serializers/json_spec.rb +++ b/spec/lib/serializers/json_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fast_spec_helper' +require 'oj' RSpec.describe Serializers::Json do describe '.dump' do diff --git a/spec/lib/serializers/symbolized_json_spec.rb b/spec/lib/serializers/symbolized_json_spec.rb index b30fb074ddd..b9217854d9a 100644 --- a/spec/lib/serializers/symbolized_json_spec.rb +++ b/spec/lib/serializers/symbolized_json_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'fast_spec_helper' +require 'oj' RSpec.describe Serializers::SymbolizedJson do describe '.dump' do diff --git a/spec/lib/sidebars/concerns/work_item_hierarchy_spec.rb b/spec/lib/sidebars/concerns/work_item_hierarchy_spec.rb new file mode 100644 index 00000000000..2120341bf23 --- /dev/null +++ b/spec/lib/sidebars/concerns/work_item_hierarchy_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Concerns::WorkItemHierarchy do + shared_examples 'hierarchy menu' do + let(:item_id) { :hierarchy } + specify { is_expected.not_to be_nil } + end + + describe 'Project hierarchy menu item' do + let_it_be_with_reload(:project) { create(:project, :repository) } + + let(:user) { project.owner } + let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } + + subject { Sidebars::Projects::Menus::ProjectInformationMenu.new(context).renderable_items.index { |e| e.item_id == item_id } } + + it_behaves_like 'hierarchy menu' + end +end diff --git a/spec/lib/sidebars/projects/menus/analytics_menu_spec.rb b/spec/lib/sidebars/projects/menus/analytics_menu_spec.rb index 6f2ca719bc9..25a65015847 100644 --- a/spec/lib/sidebars/projects/menus/analytics_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/analytics_menu_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do create(:user).tap { |u| project.add_guest(u) } end - let(:owner) { project.owner } + let(:owner) { project.first_owner } let(:current_user) { owner } let(:context) { Sidebars::Projects::Context.new(current_user: current_user, container: project, current_ref: project.repository.root_ref) } diff --git a/spec/lib/sidebars/projects/menus/ci_cd_menu_spec.rb b/spec/lib/sidebars/projects/menus/ci_cd_menu_spec.rb index dee2716e4c2..2ceb9dcada9 100644 --- a/spec/lib/sidebars/projects/menus/ci_cd_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/ci_cd_menu_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::CiCdMenu do let(:project) { build(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:can_view_pipeline_editor) { true } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, current_ref: 'master', can_view_pipeline_editor: can_view_pipeline_editor) } diff --git a/spec/lib/sidebars/projects/menus/confluence_menu_spec.rb b/spec/lib/sidebars/projects/menus/confluence_menu_spec.rb index e3ae3add4fd..836c6d26c6c 100644 --- a/spec/lib/sidebars/projects/menus/confluence_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/confluence_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::ConfluenceMenu do let_it_be_with_refind(:project) { create(:project, has_external_wiki: true) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } subject { described_class.new(context) } diff --git a/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb b/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb index 3149c316c63..56eb082e101 100644 --- a/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/deployments_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::DeploymentsMenu do let_it_be(:project) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } describe '#render?' do diff --git a/spec/lib/sidebars/projects/menus/external_issue_tracker_menu_spec.rb b/spec/lib/sidebars/projects/menus/external_issue_tracker_menu_spec.rb index 0585eb2254c..2033d40897e 100644 --- a/spec/lib/sidebars/projects/menus/external_issue_tracker_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/external_issue_tracker_menu_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::ExternalIssueTrackerMenu do let(:project) { build(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:jira_issues_integration_active) { false } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, jira_issues_integration: jira_issues_integration_active) } diff --git a/spec/lib/sidebars/projects/menus/external_wiki_menu_spec.rb b/spec/lib/sidebars/projects/menus/external_wiki_menu_spec.rb index a8f4b039b8c..9cf2d19f85c 100644 --- a/spec/lib/sidebars/projects/menus/external_wiki_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/external_wiki_menu_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::ExternalWikiMenu do let(:project) { build(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } subject { described_class.new(context) } diff --git a/spec/lib/sidebars/projects/menus/hidden_menu_spec.rb b/spec/lib/sidebars/projects/menus/hidden_menu_spec.rb index 44013898721..e64b0de9c62 100644 --- a/spec/lib/sidebars/projects/menus/hidden_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/hidden_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::HiddenMenu do let_it_be(:project) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, current_ref: project.repository.root_ref) } describe '#render?' do diff --git a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb index 55281171634..0e415ec6014 100644 --- a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::InfrastructureMenu do let(:project) { build(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, show_cluster_hint: false) } describe '#render?' do diff --git a/spec/lib/sidebars/projects/menus/invite_team_members_menu_spec.rb b/spec/lib/sidebars/projects/menus/invite_team_members_menu_spec.rb index df9b260d211..9838aa8c3e3 100644 --- a/spec/lib/sidebars/projects/menus/invite_team_members_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/invite_team_members_menu_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Sidebars::Projects::Menus::InviteTeamMembersMenu do subject(:invite_menu) { described_class.new(context) } context 'when the project is viewed by an owner of the group' do - let(:owner) { project.owner } + let(:owner) { project.first_owner } describe '#render?' do it 'renders the Invite team members link' do diff --git a/spec/lib/sidebars/projects/menus/issues_menu_spec.rb b/spec/lib/sidebars/projects/menus/issues_menu_spec.rb index e5d486bbe8f..4c0016a77a1 100644 --- a/spec/lib/sidebars/projects/menus/issues_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/issues_menu_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::IssuesMenu do let(:project) { build(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } subject { described_class.new(context) } diff --git a/spec/lib/sidebars/projects/menus/merge_requests_menu_spec.rb b/spec/lib/sidebars/projects/menus/merge_requests_menu_spec.rb index cef303fb068..45c49500e46 100644 --- a/spec/lib/sidebars/projects/menus/merge_requests_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/merge_requests_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::MergeRequestsMenu do let_it_be(:project) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } subject { described_class.new(context) } diff --git a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb index 77efe99aaa9..e8c6fb790c3 100644 --- a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::MonitorMenu do let_it_be_with_refind(:project) { create(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:show_cluster_hint) { true } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, show_cluster_hint: show_cluster_hint) } diff --git a/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb b/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb index d6807451a25..afe0b2a8951 100644 --- a/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::PackagesRegistriesMenu do let_it_be(:project) { create(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } subject { described_class.new(context) } diff --git a/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb b/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb index 7e8d0ab0518..24625413ded 100644 --- a/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::ProjectInformationMenu do let_it_be_with_reload(:project) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } describe '#container_html_options' do @@ -59,5 +59,11 @@ RSpec.describe Sidebars::Projects::Menus::ProjectInformationMenu do specify { is_expected.to be_nil } end end + + describe 'Hierarchy' do + let(:item_id) { :hierarchy } + + specify { is_expected.not_to be_nil } + end end end diff --git a/spec/lib/sidebars/projects/menus/repository_menu_spec.rb b/spec/lib/sidebars/projects/menus/repository_menu_spec.rb index 554a4e3f532..fc181947e60 100644 --- a/spec/lib/sidebars/projects/menus/repository_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/repository_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::RepositoryMenu do let_it_be(:project) { create(:project, :repository) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, current_ref: 'master') } subject { described_class.new(context) } diff --git a/spec/lib/sidebars/projects/menus/scope_menu_spec.rb b/spec/lib/sidebars/projects/menus/scope_menu_spec.rb index 980ab2f7c71..4e87f3b8ead 100644 --- a/spec/lib/sidebars/projects/menus/scope_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/scope_menu_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::ScopeMenu do let(:project) { build(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } describe '#container_html_options' do diff --git a/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb b/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb index 6e84beeb274..41158bd58dc 100644 --- a/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::SecurityComplianceMenu do let_it_be(:project) { create(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:show_promotions) { true } let(:show_discover_project_security) { true } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, show_promotions: show_promotions, show_discover_project_security: show_discover_project_security) } diff --git a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb index 1e5d41dfec4..d6136dddf40 100644 --- a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::SettingsMenu do let_it_be(:project) { create(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } subject { described_class.new(context) } diff --git a/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb b/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb index 534267a329e..e74647894fa 100644 --- a/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::ShimoMenu do let_it_be_with_reload(:project) { create(:project) } - let(:context) { Sidebars::Projects::Context.new(current_user: project.owner, container: project) } + let(:context) { Sidebars::Projects::Context.new(current_user: project.first_owner, container: project) } subject(:shimo_menu) { described_class.new(context) } diff --git a/spec/lib/sidebars/projects/menus/snippets_menu_spec.rb b/spec/lib/sidebars/projects/menus/snippets_menu_spec.rb index af219e4a742..04b8c128e3d 100644 --- a/spec/lib/sidebars/projects/menus/snippets_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/snippets_menu_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::SnippetsMenu do let(:project) { build(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } subject { described_class.new(context) } diff --git a/spec/lib/sidebars/projects/menus/wiki_menu_spec.rb b/spec/lib/sidebars/projects/menus/wiki_menu_spec.rb index 41447ee24a9..362da3e7b50 100644 --- a/spec/lib/sidebars/projects/menus/wiki_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/wiki_menu_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Sidebars::Projects::Menus::WikiMenu do let(:project) { build(:project) } - let(:user) { project.owner } + let(:user) { project.first_owner } let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } subject { described_class.new(context) } diff --git a/spec/lib/system_check/incoming_email/imap_authentication_check_spec.rb b/spec/lib/system_check/incoming_email/imap_authentication_check_spec.rb index d7a77a84472..9c4aebaedd8 100644 --- a/spec/lib/system_check/incoming_email/imap_authentication_check_spec.rb +++ b/spec/lib/system_check/incoming_email/imap_authentication_check_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' MAIL_ROOM_CONFIG_ENABLED_SAMPLE = ":mailboxes:\n"\ |