diff options
Diffstat (limited to 'spec/lib/gitlab')
68 files changed, 2263 insertions, 443 deletions
diff --git a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb new file mode 100644 index 00000000000..726a3c1c83a --- /dev/null +++ b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Gitlab::Auth::BlockedUserTracker do + set(:user) { create(:user) } + + describe '.log_if_user_blocked' do + it 'does not log if user failed to login due to undefined reason' do + expect_any_instance_of(SystemHooksService).not_to receive(:execute_hooks_for) + + expect(described_class.log_if_user_blocked({})).to be_nil + end + + it 'gracefully handles malformed environment variables' do + env = { 'warden.options' => 'test' } + + expect(described_class.log_if_user_blocked(env)).to be_nil + end + + context 'failed login due to blocked user' do + let(:env) do + { + 'warden.options' => { message: User::BLOCKED_MESSAGE }, + described_class::ACTIVE_RECORD_REQUEST_PARAMS => { 'user' => { 'login' => user.username } } + } + end + + subject { described_class.log_if_user_blocked(env) } + + before do + expect_any_instance_of(SystemHooksService).to receive(:execute_hooks_for).with(user, :failed_login) + end + + it 'logs a blocked user' do + user.block! + + expect(subject).to be_truthy + end + + it 'logs a blocked user by e-mail' do + user.block! + env[described_class::ACTIVE_RECORD_REQUEST_PARAMS]['user']['login'] = user.email + + expect(subject).to be_truthy + end + + it 'logs a LDAP blocked user' do + user.ldap_block! + + expect(subject).to be_truthy + end + end + end +end diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb index 4637816570c..2733eef6611 100644 --- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb @@ -76,6 +76,16 @@ describe Gitlab::Auth::UserAuthFinders do expect(find_user_from_rss_token).to be_nil end end + + context 'when the request format is empty' do + it 'the method call does not modify the original value' do + env['action_dispatch.request.formats'] = nil + + find_user_from_rss_token + + expect(env['action_dispatch.request.formats']).to be_nil + end + end end describe '#find_user_from_access_token' do diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index a6fbec295b5..cc202ce8bca 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -136,8 +136,8 @@ describe Gitlab::Auth do it 'grants deploy key write permissions' do project = create(:project) - key = create(:deploy_key, can_push: true) - create(:deploy_keys_project, deploy_key: key, project: project) + key = create(:deploy_key) + create(:deploy_keys_project, :write_access, deploy_key: key, project: project) token = Gitlab::LfsToken.new(key).token expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") @@ -146,7 +146,7 @@ describe Gitlab::Auth do it 'does not grant deploy key write permissions' do project = create(:project) - key = create(:deploy_key, can_push: true) + key = create(:deploy_key) token = Gitlab::LfsToken.new(key).token expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb new file mode 100644 index 00000000000..21a791f5695 --- /dev/null +++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, :migration, schema: 20180105212544 do + let(:projects_table) { table(:projects) } + let(:merge_requests_table) { table(:merge_requests) } + let(:merge_request_diffs_table) { table(:merge_request_diffs) } + let(:merge_request_diff_commits_table) { table(:merge_request_diff_commits) } + + let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') } + let(:merge_request) do + merge_requests_table.create!(target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: 'mr name', + title: 'mr name') + end + + def create_diff!(name, commits: 0) + mr_diff = merge_request_diffs_table.create!( + merge_request_id: merge_request.id) + + commits.times do |i| + merge_request_diff_commits_table.create!( + merge_request_diff_id: mr_diff.id, + relative_order: i, sha: i) + end + + mr_diff + end + + describe '#perform' do + it 'migrates diffs that have no commits' do + diff = create_diff!('with_multiple_commits', commits: 0) + + subject.perform(diff.id, diff.id) + + expect(diff.reload.commits_count).to eq(0) + end + + it 'migrates multiple diffs to the correct values' do + diffs = Array.new(3).map.with_index { |_, i| create_diff!(i, commits: 3) } + + subject.perform(diffs.first.id, diffs.last.id) + + diffs.each do |diff| + expect(diff.reload.commits_count).to eq(3) + end + end + end +end diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb index 84d9e635810..c8df6dd2118 100644 --- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb +++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :truncate, :migration, schema: 20171114162227 do +describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do let(:merge_request_diffs) { table(:merge_request_diffs) } let(:merge_requests) { table(:merge_requests) } @@ -10,6 +10,15 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t let(:merge_request_diff) { MergeRequest.find(merge_request.id).create_merge_request_diff } let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) } + before do + allow_any_instance_of(MergeRequestDiff) + .to receive(:commits_count=).and_return(nil) + end + + after do + [Project, MergeRequest, MergeRequestDiff].each(&:reset_column_information) + end + def diffs_to_hashes(diffs) diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access) end diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb index 7b5a00c6111..021e1d14b18 100644 --- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do +describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete do let(:migration) { described_class.new } before do @@ -8,7 +8,7 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do end describe '#perform' do - it 'renames the path of system-uploads', :truncate do + it 'renames the path of system-uploads' do upload = create(:upload, model: create(:project), path: 'uploads/system/project/avatar.jpg') migration.perform('uploads/system/', 'uploads/-/system/') diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb index dfe3b31f1c0..4cdb679c97f 100644 --- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb @@ -1,6 +1,16 @@ require 'rails_helper' describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, :migration, schema: 20171128214150 do + # commits_count attribute is added in a next migration + before do + allow_any_instance_of(MergeRequestDiff) + .to receive(:commits_count=).and_return(nil) + end + + after do + [MergeRequest, MergeRequestDiff].each(&:reset_column_information) + end + describe '#perform' do let(:mr_with_event) { create(:merge_request) } let!(:merged_event) { create(:event, :merged, target: mr_with_event) } diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index b5d86df09d2..f302e412a6e 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -74,14 +74,18 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do importer.create_project_if_needed end - it 'creates the Git repo in disk' do + it 'creates the Git repo on disk with the proper symlink for hooks' do create_bare_repository("#{project_path}.git") importer.create_project_if_needed project = Project.find_by_full_path(project_path) + repo_path = File.join(project.repository_storage_path, project.disk_path + '.git') + hook_path = File.join(repo_path, 'hooks') - expect(File).to exist(File.join(project.repository_storage_path, project.disk_path + '.git')) + expect(File).to exist(repo_path) + expect(File.symlink?(hook_path)).to be true + expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path) end context 'hashed storage enabled' do diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb index 05e2d94cbd6..7549e9941b6 100644 --- a/spec/lib/gitlab/ci/ansi2html_spec.rb +++ b/spec/lib/gitlab/ci/ansi2html_spec.rb @@ -217,11 +217,58 @@ describe Gitlab::Ci::Ansi2html do "#{section_end[0...-5]}</div>" end - it "prints light red" do - text = "#{section_start}\e[91mHello\e[0m\n#{section_end}" - html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}} + shared_examples 'forbidden char in section_name' do + it 'ignores sections' do + text = "#{section_start}Some text#{section_end}" + html = text.gsub("\033[0K", '').gsub('<', '<') - expect(convert_html(text)).to eq(html) + expect(convert_html(text)).to eq(html) + end + end + + shared_examples 'a legit section' do + let(:text) { "#{section_start}Some text#{section_end}" } + + it 'prints light red' do + text = "#{section_start}\e[91mHello\e[0m\n#{section_end}" + html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}} + + expect(convert_html(text)).to eq(html) + end + + it 'begins with a section_start html marker' do + expect(convert_html(text)).to start_with(section_start_html) + end + + it 'ends with a section_end html marker' do + expect(convert_html(text)).to end_with(section_end_html) + end + end + + it_behaves_like 'a legit section' + + context 'section name includes $' do + let(:section_name) { 'my_$ection'} + + it_behaves_like 'forbidden char in section_name' + end + + context 'section name includes <' do + let(:section_name) { '<a_tag>'} + + it_behaves_like 'forbidden char in section_name' + end + + context 'section name contains .-_' do + let(:section_name) { 'a.Legit-SeCtIoN_namE' } + + it_behaves_like 'a legit section' + end + + it 'do not allow XSS injections' do + text = "#{section_start}section_end:1:2<script>alert('XSS Hack!');</script>#{section_end}" + + expect(convert_html(text)).not_to include('<script>') end end diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb index 5d4de60bc8a..3cbf19bea8b 100644 --- a/spec/lib/gitlab/ci/config/entry/key_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb @@ -4,6 +4,26 @@ describe Gitlab::Ci::Config::Entry::Key do let(:entry) { described_class.new(config) } describe 'validations' do + shared_examples 'key with slash' do + it 'is invalid' do + expect(entry).not_to be_valid + end + + it 'reports errors with config value' do + expect(entry.errors).to include 'key config cannot contain the "/" character' + end + end + + shared_examples 'key with only dots' do + it 'is invalid' do + expect(entry).not_to be_valid + end + + it 'reports errors with config value' do + expect(entry.errors).to include 'key config cannot be "." or ".."' + end + end + context 'when entry config value is correct' do let(:config) { 'test' } @@ -30,6 +50,48 @@ describe Gitlab::Ci::Config::Entry::Key do end end end + + context 'when entry value contains slash' do + let(:config) { 'key/with/some/slashes' } + + it_behaves_like 'key with slash' + end + + context 'when entry value contains URI encoded slash (%2F)' do + let(:config) { 'key%2Fwith%2Fsome%2Fslashes' } + + it_behaves_like 'key with slash' + end + + context 'when entry value is a dot' do + let(:config) { '.' } + + it_behaves_like 'key with only dots' + end + + context 'when entry value is two dots' do + let(:config) { '..' } + + it_behaves_like 'key with only dots' + end + + context 'when entry value is a URI encoded dot (%2E)' do + let(:config) { '%2e' } + + it_behaves_like 'key with only dots' + end + + context 'when entry value is two URI encoded dots (%2E)' do + let(:config) { '%2E%2e' } + + it_behaves_like 'key with only dots' + end + + context 'when entry value is one dot and one URI encoded dot' do + let(:config) { '.%2e' } + + it_behaves_like 'key with only dots' + end end describe '.default' do diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb index 8c25f72804b..d612d29e3e0 100644 --- a/spec/lib/gitlab/ci/status/build/action_spec.rb +++ b/spec/lib/gitlab/ci/status/build/action_spec.rb @@ -37,16 +37,16 @@ describe Gitlab::Ci::Status::Build::Action do describe '.matches?' do subject { described_class.matches?(build, user) } - context 'when build is an action' do - let(:build) { create(:ci_build, :manual) } + context 'when build is playable action' do + let(:build) { create(:ci_build, :playable) } it 'is a correct match' do expect(subject).to be true end end - context 'when build is not manual' do - let(:build) { create(:ci_build) } + context 'when build is not playable action' do + let(:build) { create(:ci_build, :non_playable) } it 'does not match' do expect(subject).to be false diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 43761c2fe0c..1de3a14b809 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -1038,7 +1038,7 @@ describe Gitlab::Database::MigrationHelpers do end describe '#change_column_type_using_background_migration' do - let!(:issue) { create(:issue) } + let!(:issue) { create(:issue, :closed, closed_at: Time.zone.now) } let(:issue_model) do Class.new(ActiveRecord::Base) do diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 596cc435bd9..cc7cb3f23fd 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :truncate do +describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete do let(:migration) { FakeRenameReservedPathMigrationV1.new } let(:subject) { described_class.new(['the-path'], migration) } diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index 1143182531f..f31475dbd71 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :truncate do +describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete do let(:migration) { FakeRenameReservedPathMigrationV1.new } let(:subject) { described_class.new(['the-path'], migration) } let(:namespace) { create(:group, name: 'the-path') } diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb index e850b5cd6a4..0958144643b 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :truncate do +describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete do let(:migration) { FakeRenameReservedPathMigrationV1.new } let(:subject) { described_class.new(['the-path'], migration) } let(:project) do diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb index 7695b95dc57..1d31f96159c 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb @@ -13,7 +13,7 @@ shared_examples 'renames child namespaces' do |type| end end -describe Gitlab::Database::RenameReservedPathsMigration::V1, :truncate do +describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do let(:subject) { FakeRenameReservedPathMigrationV1.new } before do diff --git a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb new file mode 100644 index 00000000000..5d22dcfb508 --- /dev/null +++ b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Gitlab::Git::AttributesAtRefParser, seed_helper: true do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + + subject { described_class.new(repository, 'lfs') } + + it 'loads .gitattributes blob' do + repository.raw # Initialize repository in advance since this also checks attributes + + expected_filter = 'filter=lfs diff=lfs merge=lfs' + receive_blob = receive(:new).with(a_string_including(expected_filter)) + expect(Gitlab::Git::AttributesParser).to receive_blob.and_call_original + + subject + end + + it 'handles missing blobs' do + expect { described_class.new(repository, 'non-existant-branch') }.not_to raise_error + end + + describe '#attributes' do + it 'returns the attributes as a Hash' do + expect(subject.attributes('test.lfs')['filter']).to eq('lfs') + end + end +end diff --git a/spec/lib/gitlab/git/attributes_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb index b715fc3410a..323334e99a5 100644 --- a/spec/lib/gitlab/git/attributes_spec.rb +++ b/spec/lib/gitlab/git/attributes_parser_spec.rb @@ -1,11 +1,10 @@ require 'spec_helper' -describe Gitlab::Git::Attributes, seed_helper: true do - let(:path) do - File.join(SEED_STORAGE_PATH, 'with-git-attributes.git') - end +describe Gitlab::Git::AttributesParser, seed_helper: true do + let(:attributes_path) { File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info', 'attributes') } + let(:data) { File.read(attributes_path) } - subject { described_class.new(path) } + subject { described_class.new(data) } describe '#attributes' do context 'using a path with attributes' do @@ -66,6 +65,26 @@ describe Gitlab::Git::Attributes, seed_helper: true do expect(subject.attributes('test.foo')).to eq({}) end end + + context 'when attributes data is a file handle' do + subject do + File.open(attributes_path, 'r') do |file_handle| + described_class.new(file_handle) + end + end + + it 'returns the attributes as a Hash' do + expect(subject.attributes('test.txt')).to eq({ 'text' => true }) + end + end + + context 'when attributes data is nil' do + let(:data) { nil } + + it 'returns an empty Hash' do + expect(subject.attributes('test.foo')).to eq({}) + end + end end describe '#patterns' do @@ -74,14 +93,14 @@ describe Gitlab::Git::Attributes, seed_helper: true do end it 'parses an entry that uses a tab to separate the pattern and attributes' do - expect(subject.patterns[File.join(path, '*.md')]) + expect(subject.patterns[File.join('/', '*.md')]) .to eq({ 'gitlab-language' => 'markdown' }) end it 'stores patterns in reverse order' do first = subject.patterns.to_a[0] - expect(first[0]).to eq(File.join(path, 'bla/bla.txt')) + expect(first[0]).to eq(File.join('/', 'bla/bla.txt')) end # It's a bit hard to test for something _not_ being processed. As such we'll @@ -89,14 +108,6 @@ describe Gitlab::Git::Attributes, seed_helper: true do it 'ignores any comments and empty lines' do expect(subject.patterns.length).to eq(10) end - - it 'does not parse anything when the attributes file does not exist' do - expect(File).to receive(:exist?) - .with(File.join(path, 'info/attributes')) - .and_return(false) - - expect(subject.patterns).to eq({}) - end end describe '#parse_attributes' do @@ -132,17 +143,9 @@ describe Gitlab::Git::Attributes, seed_helper: true do expect { |b| subject.each_line(&b) }.to yield_successive_args(*args) end - it 'does not yield when the attributes file does not exist' do - expect(File).to receive(:exist?) - .with(File.join(path, 'info/attributes')) - .and_return(false) - - expect { |b| subject.each_line(&b) }.not_to yield_control - end - it 'does not yield when the attributes file has an unsupported encoding' do - path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git') - attrs = described_class.new(path) + path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git', 'info', 'attributes') + attrs = described_class.new(File.read(path)) expect { |b| attrs.each_line(&b) }.not_to yield_control end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 07eb5b82d5f..8ac960133c5 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -16,6 +16,18 @@ describe Gitlab::Git::Blob, seed_helper: true do end shared_examples 'finding blobs' do + context 'nil path' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, nil) } + + it { expect(blob).to eq(nil) } + end + + context 'blank path' do + let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, '') } + + it { expect(blob).to eq(nil) } + end + context 'file in subdir' do let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") } @@ -248,29 +260,57 @@ describe Gitlab::Git::Blob, seed_helper: true do ) end - it 'returns a list of Gitlab::Git::Blob' do - blobs = described_class.batch_lfs_pointers(repository, [lfs_blob.id]) + shared_examples 'fetching batch of LFS pointers' do + it 'returns a list of Gitlab::Git::Blob' do + blobs = described_class.batch_lfs_pointers(repository, [lfs_blob.id]) - expect(blobs.count).to eq(1) - expect(blobs).to all( be_a(Gitlab::Git::Blob) ) - end + expect(blobs.count).to eq(1) + expect(blobs).to all( be_a(Gitlab::Git::Blob) ) + end - it 'silently ignores tree objects' do - blobs = described_class.batch_lfs_pointers(repository, [tree_object.oid]) + it 'accepts blob IDs as a lazy enumerator' do + blobs = described_class.batch_lfs_pointers(repository, [lfs_blob.id].lazy) - expect(blobs).to eq([]) - end + expect(blobs.count).to eq(1) + expect(blobs).to all( be_a(Gitlab::Git::Blob) ) + end + + it 'handles empty list of IDs gracefully' do + blobs_1 = described_class.batch_lfs_pointers(repository, [].lazy) + blobs_2 = described_class.batch_lfs_pointers(repository, []) - it 'silently ignores non lfs objects' do - blobs = described_class.batch_lfs_pointers(repository, [non_lfs_blob.id]) + expect(blobs_1).to eq([]) + expect(blobs_2).to eq([]) + end + + it 'silently ignores tree objects' do + blobs = described_class.batch_lfs_pointers(repository, [tree_object.oid]) + + expect(blobs).to eq([]) + end + + it 'silently ignores non lfs objects' do + blobs = described_class.batch_lfs_pointers(repository, [non_lfs_blob.id]) - expect(blobs).to eq([]) + expect(blobs).to eq([]) + end + + it 'avoids loading large blobs into memory' do + # This line could call `lookup` on `repository`, so do here before mocking. + non_lfs_blob_id = non_lfs_blob.id + + expect(repository).not_to receive(:lookup) + + described_class.batch_lfs_pointers(repository, [non_lfs_blob_id]) + end end - it 'avoids loading large blobs into memory' do - expect(repository).not_to receive(:lookup) + context 'when Gitaly batch_lfs_pointers is enabled' do + it_behaves_like 'fetching batch of LFS pointers' + end - described_class.batch_lfs_pointers(repository, [non_lfs_blob.id]) + context 'when Gitaly batch_lfs_pointers is disabled', :disable_gitaly do + it_behaves_like 'fetching batch of LFS pointers' end end diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 6a07a3ca8b8..85e6efd7ca2 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -388,6 +388,84 @@ describe Gitlab::Git::Commit, seed_helper: true do end end end + + describe '.extract_signature' do + subject { described_class.extract_signature(repository, commit_id) } + + shared_examples '.extract_signature' do + context 'when the commit is signed' do + let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } + + it 'returns signature and signed text' do + signature, signed_text = subject + + expected_signature = <<~SIGNATURE + -----BEGIN PGP SIGNATURE----- + Version: GnuPG/MacGPG2 v2.0.22 (Darwin) + Comment: GPGTools - https://gpgtools.org + + iQEcBAABCgAGBQJTDvaZAAoJEGJ8X1ifRn8XfvYIAMuB0yrbTGo1BnOSoDfyrjb0 + Kw2EyUzvXYL72B63HMdJ+/0tlSDC6zONF3fc+bBD8z+WjQMTbwFNMRbSSy2rKEh+ + mdRybOP3xBIMGgEph0/kmWln39nmFQBsPRbZBWoU10VfI/ieJdEOgOphszgryRar + TyS73dLBGE9y9NIININVaNISet9D9QeXFqc761CGjh4YIghvPpi+YihMWapGka6v + hgKhX+hc5rj+7IEE0CXmlbYR8OYvAbAArc5vJD7UTxAY4Z7/l9d6Ydt9GQ25khfy + ANFgltYzlR6evLFmDjssiP/mx/ZMN91AL0ueJ9nNGv411Mu2CUW+tDCaQf35mdc= + =j51i + -----END PGP SIGNATURE----- + SIGNATURE + + expect(signature).to eq(expected_signature.chomp) + expect(signature).to be_a_binary_string + + expected_signed_text = <<~SIGNED_TEXT + tree 22bfa2fbd217df24731f43ff43a4a0f8db759dae + parent ae73cb07c9eeaf35924a10f713b364d32b2dd34f + author Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200 + committer Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200 + + Feature added + + Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> + SIGNED_TEXT + + expect(signed_text).to eq(expected_signed_text) + expect(signed_text).to be_a_binary_string + end + end + + context 'when the commit has no signature' do + let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' } + + it 'returns nil' do + expect(subject).to be_nil + end + end + + context 'when the commit cannot be found' do + let(:commit_id) { Gitlab::Git::BLANK_SHA } + + it 'returns nil' do + expect(subject).to be_nil + end + end + + context 'when the commit ID is invalid' do + let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e' } + + it 'raises ArgumentError' do + expect { subject }.to raise_error(ArgumentError) + end + end + end + + context 'with gitaly' do + it_behaves_like '.extract_signature' + end + + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like '.extract_signature' + end + end end describe '#init_from_rugged' do diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb index a798b188a0d..f4b964e1ee9 100644 --- a/spec/lib/gitlab/git/gitlab_projects_spec.rb +++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb @@ -25,51 +25,6 @@ describe Gitlab::Git::GitlabProjects do it { expect(gl_projects.logger).to eq(logger) } end - describe '#mv_project' do - let(:new_repo_path) { File.join(tmp_repos_path, 'repo.git') } - - it 'moves a repo directory' do - expect(File.exist?(tmp_repo_path)).to be_truthy - - message = "Moving repository from <#{tmp_repo_path}> to <#{new_repo_path}>." - expect(logger).to receive(:info).with(message) - - expect(gl_projects.mv_project('repo.git')).to be_truthy - - expect(File.exist?(tmp_repo_path)).to be_falsy - expect(File.exist?(new_repo_path)).to be_truthy - end - - it "fails if the source path doesn't exist" do - expected_source_path = File.join(tmp_repos_path, 'bad-src.git') - expect(logger).to receive(:error).with("mv-project failed: source path <#{expected_source_path}> does not exist.") - - result = build_gitlab_projects(tmp_repos_path, 'bad-src.git').mv_project('repo.git') - expect(result).to be_falsy - end - - it 'fails if the destination path already exists' do - FileUtils.mkdir_p(File.join(tmp_repos_path, 'already-exists.git')) - - expected_distination_path = File.join(tmp_repos_path, 'already-exists.git') - message = "mv-project failed: destination path <#{expected_distination_path}> already exists." - expect(logger).to receive(:error).with(message) - - expect(gl_projects.mv_project('already-exists.git')).to be_falsy - end - end - - describe '#rm_project' do - it 'removes a repo directory' do - expect(File.exist?(tmp_repo_path)).to be_truthy - expect(logger).to receive(:info).with("Removing repository <#{tmp_repo_path}>.") - - expect(gl_projects.rm_project).to be_truthy - - expect(File.exist?(tmp_repo_path)).to be_falsy - end - end - describe '#push_branches' do let(:remote_name) { 'remote-name' } let(:branch_name) { 'master' } @@ -203,39 +158,55 @@ describe Gitlab::Git::GitlabProjects do subject { gl_projects.import_project(import_url, timeout) } - context 'success import' do - it 'imports a repo' do - expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy + shared_examples 'importing repository' do + context 'success import' do + it 'imports a repo' do + expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy - message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>." - expect(logger).to receive(:info).with(message) + is_expected.to be_truthy - is_expected.to be_truthy + expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy + end + end + + context 'already exists' do + it "doesn't import" do + FileUtils.mkdir_p(tmp_repo_path) - expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy + is_expected.to be_falsy + end end end - context 'already exists' do - it "doesn't import" do - FileUtils.mkdir_p(tmp_repo_path) + context 'when Gitaly import_repository feature is enabled' do + it_behaves_like 'importing repository' + end - is_expected.to be_falsy + context 'when Gitaly import_repository feature is disabled', :disable_gitaly do + describe 'logging' do + it 'imports a repo' do + message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>." + expect(logger).to receive(:info).with(message) + + subject + end end - end - context 'timeout' do - it 'does not import a repo' do - stub_spawn_timeout(cmd, timeout, nil) + context 'timeout' do + it 'does not import a repo' do + stub_spawn_timeout(cmd, timeout, nil) - message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed." - expect(logger).to receive(:error).with(message) + message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed." + expect(logger).to receive(:error).with(message) - is_expected.to be_falsy + is_expected.to be_falsy - expect(gl_projects.output).to eq("Timed out\n") - expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy + expect(gl_projects.output).to eq("Timed out\n") + expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy + end end + + it_behaves_like 'importing repository' end end @@ -248,6 +219,9 @@ describe Gitlab::Git::GitlabProjects do before do FileUtils.mkdir_p(dest_repos_path) + + # Undo spec_helper stub that deletes hooks + allow_any_instance_of(described_class).to receive(:fork_repository).and_call_original end after do diff --git a/spec/lib/gitlab/git/info_attributes_spec.rb b/spec/lib/gitlab/git/info_attributes_spec.rb new file mode 100644 index 00000000000..ea84909c3e0 --- /dev/null +++ b/spec/lib/gitlab/git/info_attributes_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe Gitlab::Git::InfoAttributes, seed_helper: true do + let(:path) do + File.join(SEED_STORAGE_PATH, 'with-git-attributes.git') + end + + subject { described_class.new(path) } + + describe '#attributes' do + context 'using a path with attributes' do + it 'returns the attributes as a Hash' do + expect(subject.attributes('test.txt')).to eq({ 'text' => true }) + end + + it 'returns an empty Hash for a defined path without attributes' do + expect(subject.attributes('bla/bla.txt')).to eq({}) + end + end + end + + describe '#parser' do + it 'parses a file with entries' do + expect(subject.patterns).to be_an_instance_of(Hash) + expect(subject.patterns["/*.txt"]).to eq({ 'text' => true }) + end + + it 'does not parse anything when the attributes file does not exist' do + expect(File).to receive(:exist?) + .with(File.join(path, 'info/attributes')) + .and_return(false) + + expect(subject.patterns).to eq({}) + end + + it 'does not parse attributes files with unsupported encoding' do + path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git') + subject = described_class.new(path) + + expect(subject.patterns).to eq({}) + end + end +end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index f346a345f00..96a442f782f 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -2,6 +2,7 @@ require "spec_helper" describe Gitlab::Git::Repository, seed_helper: true do include Gitlab::EncodingHelper + using RSpec::Parameterized::TableSyntax shared_examples 'wrapping gRPC errors' do |gitaly_client_class, gitaly_client_method| it 'wraps gRPC not found error' do @@ -19,6 +20,7 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } let(:storage_path) { TestEnv.repos_path } + let(:user) { build(:user) } describe '.create_hooks' do let(:repo_path) { File.join(storage_path, 'hook-test.git') } @@ -248,7 +250,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end shared_examples 'archive check' do |extenstion| - it { expect(metadata['ArchivePath']).to match(/tmp\/gitlab-git-test.git\/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}/) } + it { expect(metadata['ArchivePath']).to match(%r{tmp/gitlab-git-test.git/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}}) } it { expect(metadata['ArchivePath']).to end_with extenstion } end @@ -442,6 +444,7 @@ describe Gitlab::Git::Repository, seed_helper: true do shared_examples 'simple commit counting' do it { expect(repository.commit_count("master")).to eq(25) } it { expect(repository.commit_count("feature")).to eq(9) } + it { expect(repository.commit_count("does-not-exist")).to eq(0) } end context 'when Gitaly commit_count feature is enabled' do @@ -560,35 +563,39 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#delete_refs' do - before(:all) do - @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') - end + shared_examples 'deleting refs' do + let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } - it 'deletes the ref' do - @repo.delete_refs('refs/heads/feature') + after do + ensure_seeds + end - expect(@repo.rugged.references['refs/heads/feature']).to be_nil - end + it 'deletes the ref' do + repo.delete_refs('refs/heads/feature') + + expect(repo.rugged.references['refs/heads/feature']).to be_nil + end - it 'deletes all refs' do - refs = %w[refs/heads/wip refs/tags/v1.1.0] - @repo.delete_refs(*refs) + it 'deletes all refs' do + refs = %w[refs/heads/wip refs/tags/v1.1.0] + repo.delete_refs(*refs) - refs.each do |ref| - expect(@repo.rugged.references[ref]).to be_nil + refs.each do |ref| + expect(repo.rugged.references[ref]).to be_nil + end end - end - it 'raises an error if it failed' do - expect(@repo).to receive(:popen).and_return(['Error', 1]) + it 'raises an error if it failed' do + expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError) + end + end - expect do - @repo.delete_refs('refs/heads/fix') - end.to raise_error(Gitlab::Git::Repository::GitError) + context 'when Gitaly delete_refs feature is enabled' do + it_behaves_like 'deleting refs' end - after(:all) do - ensure_seeds + context 'when Gitaly delete_refs feature is disabled', :disable_gitaly do + it_behaves_like 'deleting refs' end end @@ -687,7 +694,6 @@ describe Gitlab::Git::Repository, seed_helper: true do describe '#remote_tags' do let(:remote_name) { 'upstream' } let(:target_commit_id) { SeedRepo::Commit::ID } - let(:user) { create(:user) } let(:tag_name) { 'v0.0.1' } let(:tag_message) { 'My tag' } let(:remote_repository) do @@ -899,44 +905,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - context "compare results between log_by_walk and log_by_shell" do - let(:options) { { ref: "master" } } - let(:commits_by_walk) { repository.log(options).map(&:id) } - let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:id) } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - - context "with limit" do - let(:options) { { ref: "master", limit: 1 } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - end - - context "with offset" do - let(:options) { { ref: "master", offset: 1 } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - end - - context "with skip_merges" do - let(:options) { { ref: "master", skip_merges: true } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - end - - context "with path" do - let(:options) { { ref: "master", path: "encoding" } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - - context "with follow" do - let(:options) { { ref: "master", path: "encoding", follow: true } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - end - end - end - context "where provides 'after' timestamp" do options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') } @@ -981,6 +949,16 @@ describe Gitlab::Git::Repository, seed_helper: true do end end end + + context 'limit validation' do + where(:limit) do + [0, nil, '', 'foo'] + end + + with_them do + it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) } + end + end end describe "#rugged_commits_between" do @@ -1022,6 +1000,29 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.to eq(17) } end + describe '#merge_base' do + shared_examples '#merge_base' do + where(:from, :to, :result) do + '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' + '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' + '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil + 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil + end + + with_them do + it { expect(repository.merge_base(from, to)).to eq(result) } + end + end + + context 'with gitaly' do + it_behaves_like '#merge_base' + end + + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like '#merge_base' + end + end + describe '#count_commits' do shared_examples 'extended commit counting' do context 'with after timestamp' do @@ -1104,14 +1105,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe "branch_names_contains" do - subject { repository.branch_names_contains(SeedRepo::LastCommit::ID) } - - it { is_expected.to include('master') } - it { is_expected.not_to include('feature') } - it { is_expected.not_to include('fix') } - end - describe '#autocrlf' do before(:all) do @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') @@ -1283,48 +1276,58 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#merged_branch_names' do - context 'when branch names are passed' do - it 'only returns the names we are asking' do - names = repository.merged_branch_names(%w[merge-test]) + shared_examples 'finding merged branch names' do + context 'when branch names are passed' do + it 'only returns the names we are asking' do + names = repository.merged_branch_names(%w[merge-test]) - expect(names).to contain_exactly('merge-test') - end + expect(names).to contain_exactly('merge-test') + end - it 'does not return unmerged branch names' do - names = repository.merged_branch_names(%w[feature]) + it 'does not return unmerged branch names' do + names = repository.merged_branch_names(%w[feature]) - expect(names).to be_empty + expect(names).to be_empty + end end - end - context 'when no root ref is available' do - it 'returns empty list' do - project = create(:project, :empty_repo) + context 'when no root ref is available' do + it 'returns empty list' do + project = create(:project, :empty_repo) - names = project.repository.merged_branch_names(%w[feature]) + names = project.repository.merged_branch_names(%w[feature]) - expect(names).to be_empty + expect(names).to be_empty + end end - end - context 'when no branch names are specified' do - before do - repository.create_branch('identical', 'master') - end + context 'when no branch names are specified' do + before do + repository.create_branch('identical', 'master') + end - after do - ensure_seeds - end + after do + ensure_seeds + end - it 'returns all merged branch names except for identical one' do - names = repository.merged_branch_names + it 'returns all merged branch names except for identical one' do + names = repository.merged_branch_names - expect(names).to include('merge-test') - expect(names).to include('fix-mode') - expect(names).not_to include('feature') - expect(names).not_to include('identical') + expect(names).to include('merge-test') + expect(names).to include('fix-mode') + expect(names).not_to include('feature') + expect(names).not_to include('identical') + end end end + + context 'when Gitaly merged_branch_names feature is enabled' do + it_behaves_like 'finding merged branch names' + end + + context 'when Gitaly merged_branch_names feature is disabled', :disable_gitaly do + it_behaves_like 'finding merged branch names' + end end describe "#ls_files" do @@ -1708,7 +1711,6 @@ describe Gitlab::Git::Repository, seed_helper: true do shared_examples "user deleting a branch" do let(:project) { create(:project, :repository) } let(:repository) { project.repository.raw } - let(:user) { create(:user) } let(:branch_name) { "to-be-deleted-soon" } before do @@ -1749,12 +1751,49 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + describe '#write_config' do + before do + repository.rugged.config["gitlab.fullpath"] = repository.path + end + + shared_examples 'writing repo config' do + context 'is given a path' do + it 'writes it to disk' do + repository.write_config(full_path: "not-the/real-path.git") + + config = File.read(File.join(repository.path, "config")) + + expect(config).to include("[gitlab]") + expect(config).to include("fullpath = not-the/real-path.git") + end + end + + context 'it is given an empty path' do + it 'does not write it to disk' do + repository.write_config(full_path: "") + + config = File.read(File.join(repository.path, "config")) + + expect(config).to include("[gitlab]") + expect(config).to include("fullpath = #{repository.path}") + end + end + end + + context "when gitaly_write_config is enabled" do + it_behaves_like "writing repo config" + end + + context "when gitaly_write_config is disabled", :disable_gitaly do + it_behaves_like "writing repo config" + end + end + describe '#merge' do let(:repository) do Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') end let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' } - let(:user) { build(:user) } let(:target_branch) { 'test-merge-target-branch' } before do @@ -1807,7 +1846,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } - let(:user) { build(:user) } let(:target_branch) { 'test-ff-target-branch' } before do @@ -1962,6 +2000,75 @@ describe Gitlab::Git::Repository, seed_helper: true do it { expect(subject.repository_relative_path).to eq(repository.relative_path) } end + describe '#bundle_to_disk' do + shared_examples 'bundling to disk' do + let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") } + + after do + FileUtils.rm_rf(save_path) + end + + it 'saves a bundle to disk' do + repository.bundle_to_disk(save_path) + + success = system( + *%W(#{Gitlab.config.git.bin_path} -C #{repository.path} bundle verify #{save_path}), + [:out, :err] => '/dev/null' + ) + expect(success).to be true + end + end + + context 'when Gitaly bundle_to_disk feature is enabled' do + it_behaves_like 'bundling to disk' + end + + context 'when Gitaly bundle_to_disk feature is disabled', :disable_gitaly do + it_behaves_like 'bundling to disk' + end + end + + describe '#create_from_bundle' do + shared_examples 'creating repo from bundle' do + let(:bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") } + let(:project) { create(:project) } + let(:imported_repo) { project.repository.raw } + + before do + expect(repository.bundle_to_disk(bundle_path)).to be true + end + + after do + FileUtils.rm_rf(bundle_path) + end + + it 'creates a repo from a bundle file' do + expect(imported_repo).not_to exist + + result = imported_repo.create_from_bundle(bundle_path) + + expect(result).to be true + expect(imported_repo).to exist + expect { imported_repo.fsck }.not_to raise_exception + end + + it 'creates a symlink to the global hooks dir' do + imported_repo.create_from_bundle(bundle_path) + hooks_path = File.join(imported_repo.path, 'hooks') + + expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path) + end + end + + context 'when Gitaly create_repo_from_bundle feature is enabled' do + it_behaves_like 'creating repo from bundle' + end + + context 'when Gitaly create_repo_from_bundle feature is disabled', :disable_gitaly do + it_behaves_like 'creating repo from bundle' + end + end + context 'gitlab_projects commands' do let(:gitlab_projects) { repository.gitlab_projects } let(:timeout) { Gitlab.config.gitlab_shell.git_timeout } @@ -2057,6 +2164,47 @@ describe Gitlab::Git::Repository, seed_helper: true do expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') end end + + describe '#squash' do + let(:squash_id) { '1' } + let(:branch_name) { 'fix' } + let(:start_sha) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' } + let(:end_sha) { '12d65c8dd2b2676fa3ac47d955accc085a37a9c1' } + + subject do + opts = { + branch: branch_name, + start_sha: start_sha, + end_sha: end_sha, + author: user, + message: 'Squash commit message' + } + + repository.squash(user, squash_id, opts) + end + + context 'sparse checkout' do + let(:expected_files) { %w(files files/js files/js/application.js) } + + before do + allow(repository).to receive(:with_worktree).and_wrap_original do |m, *args| + m.call(*args) do + worktree_path = args[0] + files_pattern = File.join(worktree_path, '**', '*') + expected = expected_files.map do |path| + File.expand_path(path, worktree_path) + end + + expect(Dir[files_pattern]).to eq(expected) + end + end + end + + it 'checkouts only the files in the diff' do + subject + end + end + end end def create_remote_branch(repository, remote_name, branch_name, source_branch_name) diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index eaf74951b0e..90fbef9d248 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -39,7 +39,7 @@ describe Gitlab::Git::RevList do ] expect(rev_list).to receive(:popen).with(*params) do |*_, lazy_block:| - lazy_block.call(output.split("\n").lazy) + lazy_block.call(output.lines.lazy.map(&:chomp)) end end @@ -64,6 +64,15 @@ describe Gitlab::Git::RevList do expect(rev_list.new_objects(require_path: true)).to eq(%w[sha2]) end + it 'can handle non utf-8 paths' do + non_utf_char = [0x89].pack("c*").force_encoding("UTF-8") + stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1") + + rev_list.new_objects(require_path: true) do |object_ids| + expect(object_ids.force).to eq(%w[sha2]) + end + end + it 'can yield a lazy enumerator' do stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 86f7bcb8e38..001e406a930 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -80,22 +80,18 @@ describe Gitlab::Git::Tree, seed_helper: true do end describe '#where' do - context 'with gitaly disabled' do - before do - allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) - end - - it 'calls #tree_entries_from_rugged' do - expect(described_class).to receive(:tree_entries_from_rugged) - - described_class.where(repository, SeedRepo::Commit::ID, '/') + shared_examples '#where' do + it 'returns an empty array when called with an invalid ref' do + expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([]) end end - it 'gets the tree entries from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::CommitService).to receive(:tree_entries) + context 'with gitaly' do + it_behaves_like '#where' + end - described_class.where(repository, SeedRepo::Commit::ID, '/') + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like '#where' end end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 4290fbb0087..2009a8ac48c 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -51,12 +51,12 @@ describe Gitlab::GitAccess do context 'when the project exists' do context 'when actor exists' do context 'when actor is a DeployKey' do - let(:deploy_key) { create(:deploy_key, user: user, can_push: true) } + let(:deploy_key) { create(:deploy_key, user: user) } let(:actor) { deploy_key } context 'when the DeployKey has access to the project' do before do - deploy_key.projects << project + deploy_key.deploy_keys_projects.create(project: project, can_push: true) end it 'allows push and pull access' do @@ -696,15 +696,13 @@ describe Gitlab::GitAccess do end describe 'deploy key permissions' do - let(:key) { create(:deploy_key, user: user, can_push: can_push) } + let(:key) { create(:deploy_key, user: user) } let(:actor) { key } context 'when deploy_key can push' do - let(:can_push) { true } - context 'when project is authorized' do before do - key.projects << project + key.deploy_keys_projects.create(project: project, can_push: true) end it { expect { push_access_check }.not_to raise_error } @@ -732,11 +730,9 @@ describe Gitlab::GitAccess do end context 'when deploy_key cannot push' do - let(:can_push) { false } - context 'when project is authorized' do before do - key.projects << project + key.deploy_keys_projects.create(project: project, can_push: false) end it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:deploy_key_upload]) } diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index b2275119a04..3722a91c050 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -131,6 +131,29 @@ describe Gitlab::GitalyClient::CommitService do end end + describe '#commit_count' do + before do + expect_any_instance_of(Gitaly::CommitService::Stub) + .to receive(:count_commits) + .with(gitaly_request_with_path(storage_name, relative_path), + kind_of(Hash)) + .and_return([]) + end + + it 'sends a commit_count message' do + client.commit_count(revision) + end + + context 'with UTF-8 params strings' do + let(:revision) { "branch\u011F" } + let(:path) { "foo/\u011F.txt" } + + it 'handles string encodings correctly' do + client.commit_count(revision, path: path) + end + end + end + describe '#find_commit' do let(:revision) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' } it 'sends an RPC request' do diff --git a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb new file mode 100644 index 00000000000..1c933410bd5 --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::ConflictFilesStitcher do + describe 'enumeration' do + it 'combines segregated ConflictFile messages together' do + target_project = create(:project, :repository) + target_repository = target_project.repository.raw + target_gitaly_repository = target_repository.gitaly_repository + + our_path_1 = 'our/path/1' + their_path_1 = 'their/path/1' + our_mode_1 = 0744 + commit_oid_1 = 'f00' + content_1 = 'content of the first file' + + our_path_2 = 'our/path/2' + their_path_2 = 'their/path/2' + our_mode_2 = 0600 + commit_oid_2 = 'ba7' + content_2 = 'content of the second file' + + header_1 = double(repository: target_gitaly_repository, commit_oid: commit_oid_1, + our_path: our_path_1, their_path: their_path_1, our_mode: our_mode_1) + header_2 = double(repository: target_gitaly_repository, commit_oid: commit_oid_2, + our_path: our_path_2, their_path: their_path_2, our_mode: our_mode_2) + + messages = [ + double(files: [double(header: header_1), double(header: nil, content: content_1[0..5])]), + double(files: [double(header: nil, content: content_1[6..-1])]), + double(files: [double(header: header_2)]), + double(files: [double(header: nil, content: content_2[0..5]), double(header: nil, content: content_2[6..10])]), + double(files: [double(header: nil, content: content_2[11..-1])]) + ] + + conflict_files = described_class.new(messages).to_a + + expect(conflict_files.size).to be(2) + + expect(conflict_files[0].content).to eq(content_1) + expect(conflict_files[0].their_path).to eq(their_path_1) + expect(conflict_files[0].our_path).to eq(our_path_1) + expect(conflict_files[0].our_mode).to be(our_mode_1) + expect(conflict_files[0].repository).to eq(target_repository) + expect(conflict_files[0].commit_oid).to eq(commit_oid_1) + + expect(conflict_files[1].content).to eq(content_2) + expect(conflict_files[1].their_path).to eq(their_path_2) + expect(conflict_files[1].our_path).to eq(our_path_2) + expect(conflict_files[1].our_mode).to be(our_mode_2) + expect(conflict_files[1].repository).to eq(target_repository) + expect(conflict_files[1].commit_oid).to eq(commit_oid_2) + end + end +end diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb index b9641de7eda..e4fe01a671f 100644 --- a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb @@ -19,41 +19,12 @@ describe Gitlab::GitalyClient::ConflictsService do their_commit_oid: their_commit_oid ) end - let(:our_path) { 'our/path' } - let(:their_path) { 'their/path' } - let(:our_mode) { 0744 } - let(:header) do - double(repository: target_gitaly_repository, commit_oid: our_commit_oid, - our_path: our_path, our_mode: 0744, their_path: their_path) - end - let(:response) do - [ - double(files: [double(header: header), double(content: 'foo', header: nil)]), - double(files: [double(content: 'bar', header: nil)]) - ] - end - let(:file) { subject[0] } - - subject { client.list_conflict_files } it 'sends an RPC request' do expect_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:list_conflict_files) - .with(request, kind_of(Hash)).and_return([]) - - subject - end - - it 'forms a Gitlab::Git::ConflictFile collection from the response' do - allow_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:list_conflict_files) - .with(request, kind_of(Hash)).and_return(response) + .with(request, kind_of(Hash)).and_return([].to_enum) - expect(subject.size).to be(1) - expect(file.content).to eq('foobar') - expect(file.their_path).to eq(their_path) - expect(file.our_path).to eq(our_path) - expect(file.our_mode).to be(our_mode) - expect(file.repository).to eq(target_repository) - expect(file.commit_oid).to eq(our_commit_oid) + client.list_conflict_files end end diff --git a/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb new file mode 100644 index 00000000000..2c7e5eb5787 --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::HealthCheckService do + let(:project) { create(:project) } + let(:storage_name) { project.repository_storage } + + subject { described_class.new(storage_name) } + + describe '#check' do + it 'successfully sends a health check request' do + expect(Gitlab::GitalyClient).to receive(:call).with( + storage_name, + :health_check, + :check, + instance_of(Grpc::Health::V1::HealthCheckRequest), + timeout: Gitlab::GitalyClient.fast_timeout).and_call_original + + expect(subject.check).to eq({ success: true }) + end + + it 'receives an unsuccessful health check request' do + expect_any_instance_of(Grpc::Health::V1::Health::Stub) + .to receive(:check) + .and_return(double(status: false)) + + expect(subject.check).to eq({ success: false }) + end + + it 'gracefully handles gRPC error' do + expect(Gitlab::GitalyClient).to receive(:call).with( + storage_name, + :health_check, + :check, + instance_of(Grpc::Health::V1::HealthCheckRequest), + timeout: Gitlab::GitalyClient.fast_timeout) + .and_raise(GRPC::Unavailable.new('Connection refused')) + + expect(subject.check).to eq({ success: false, message: '14:Connection refused' }) + end + end +end diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 951e146a30a..257e4c50f2d 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -112,7 +112,7 @@ describe Gitlab::GitalyClient::RefService do expect_any_instance_of(Gitaly::RefService::Stub) .to receive(:delete_refs) .with(gitaly_request_with_params(except_with_prefix: prefixes), kind_of(Hash)) - .and_return(double('delete_refs_response')) + .and_return(double('delete_refs_response', git_error: "")) client.delete_refs(except_with_prefixes: prefixes) end diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb index 9d540446532..872377c93d8 100644 --- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb @@ -44,4 +44,18 @@ describe Gitlab::GitalyClient::RemoteService do expect(client.fetch_internal_remote(remote_repository)).to be(true) end end + + describe '#update_remote_mirror' do + let(:ref_name) { 'remote_mirror_1' } + let(:only_branches_matching) { ['my-branch', 'master'] } + + it 'sends an update_remote_mirror message' do + expect_any_instance_of(Gitaly::RemoteService::Stub) + .to receive(:update_remote_mirror) + .with(kind_of(Enumerator), kind_of(Hash)) + .and_return(double(:update_remote_mirror_response)) + + client.update_remote_mirror(ref_name, only_branches_matching) + end + end end diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index 309b7338ef0..81bcd8c28ed 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -3,6 +3,31 @@ require 'spec_helper' # We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want # those stubs while testing the GitalyClient itself. describe Gitlab::GitalyClient, skip_gitaly_mock: true do + describe '.stub_class' do + it 'returns the gRPC health check stub' do + expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub) + end + + it 'returns a Gitaly stub' do + expect(described_class.stub_class(:ref_service)).to eq(::Gitaly::RefService::Stub) + end + end + + describe '.stub_address' do + it 'returns the same result after being called multiple times' do + address = 'localhost:9876' + prefixed_address = "tcp://#{address}" + + allow(Gitlab.config.repositories).to receive(:storages).and_return({ + 'default' => { 'gitaly_address' => prefixed_address } + }) + + 2.times do + expect(described_class.stub_address('default')).to eq('localhost:9876') + end + end + end + describe '.stub' do # Notice that this is referring to gRPC "stubs", not rspec stubs before do diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb index d72572cd510..44695acbe7d 100644 --- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb @@ -244,7 +244,7 @@ describe Gitlab::GithubImport::Importer::PullRequestsImporter do it 'returns true when a commit exists' do expect(project.repository) - .to receive(:lookup) + .to receive(:commit) .with('123') .and_return(double(:commit)) @@ -253,9 +253,9 @@ describe Gitlab::GithubImport::Importer::PullRequestsImporter do it 'returns false when a commit does not exist' do expect(project.repository) - .to receive(:lookup) + .to receive(:commit) .with('123') - .and_raise(Rugged::OdbError) + .and_return(nil) expect(importer.commit_exists?('123')).to eq(false) end diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb index a6c99bc07d4..e3bf2801406 100644 --- a/spec/lib/gitlab/gpg/commit_spec.rb +++ b/spec/lib/gitlab/gpg/commit_spec.rb @@ -38,8 +38,8 @@ describe Gitlab::Gpg::Commit do end before do - allow(Rugged::Commit).to receive(:extract_signature) - .with(Rugged::Repository, commit_sha) + allow(Gitlab::Git::Commit).to receive(:extract_signature) + .with(Gitlab::Git::Repository, commit_sha) .and_return( [ GpgHelpers::User1.signed_commit_signature, @@ -77,8 +77,8 @@ describe Gitlab::Gpg::Commit do end before do - allow(Rugged::Commit).to receive(:extract_signature) - .with(Rugged::Repository, commit_sha) + allow(Gitlab::Git::Commit).to receive(:extract_signature) + .with(Gitlab::Git::Repository, commit_sha) .and_return( [ GpgHelpers::User3.signed_commit_signature, @@ -116,8 +116,8 @@ describe Gitlab::Gpg::Commit do end before do - allow(Rugged::Commit).to receive(:extract_signature) - .with(Rugged::Repository, commit_sha) + allow(Gitlab::Git::Commit).to receive(:extract_signature) + .with(Gitlab::Git::Repository, commit_sha) .and_return( [ GpgHelpers::User1.signed_commit_signature, @@ -151,8 +151,8 @@ describe Gitlab::Gpg::Commit do end before do - allow(Rugged::Commit).to receive(:extract_signature) - .with(Rugged::Repository, commit_sha) + allow(Gitlab::Git::Commit).to receive(:extract_signature) + .with(Gitlab::Git::Repository, commit_sha) .and_return( [ GpgHelpers::User1.signed_commit_signature, @@ -187,8 +187,8 @@ describe Gitlab::Gpg::Commit do end before do - allow(Rugged::Commit).to receive(:extract_signature) - .with(Rugged::Repository, commit_sha) + allow(Gitlab::Git::Commit).to receive(:extract_signature) + .with(Gitlab::Git::Repository, commit_sha) .and_return( [ GpgHelpers::User1.signed_commit_signature, @@ -217,8 +217,8 @@ describe Gitlab::Gpg::Commit do let!(:commit) { create :commit, project: project, sha: commit_sha } before do - allow(Rugged::Commit).to receive(:extract_signature) - .with(Rugged::Repository, commit_sha) + allow(Gitlab::Git::Commit).to receive(:extract_signature) + .with(Gitlab::Git::Repository, commit_sha) .and_return( [ GpgHelpers::User1.signed_commit_signature, diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb index d6000af0ecd..c034eccf2a6 100644 --- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb +++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb @@ -26,8 +26,8 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do before do allow_any_instance_of(Project).to receive(:commit).and_return(commit) - allow(Rugged::Commit).to receive(:extract_signature) - .with(Rugged::Repository, commit_sha) + allow(Gitlab::Git::Commit).to receive(:extract_signature) + .with(Gitlab::Git::Repository, commit_sha) .and_return(signature) end diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb new file mode 100644 index 00000000000..724beefff69 --- /dev/null +++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe Gitlab::HealthChecks::GitalyCheck do + let(:result_class) { Gitlab::HealthChecks::Result } + let(:repository_storages) { ['default'] } + + before do + allow(described_class).to receive(:repository_storages) { repository_storages } + end + + describe '#readiness' do + subject { described_class.readiness } + + before do + expect(Gitlab::GitalyClient::HealthCheckService).to receive(:new).and_return(gitaly_check) + end + + context 'Gitaly server is up' do + let(:gitaly_check) { double(check: { success: true }) } + + it { is_expected.to eq([result_class.new(true, nil, shard: 'default')]) } + end + + context 'Gitaly server is down' do + let(:gitaly_check) { double(check: { success: false, message: 'Connection refused' }) } + + it { is_expected.to eq([result_class.new(false, 'Connection refused', shard: 'default')]) } + end + end + + describe '#metrics' do + subject { described_class.metrics } + + before do + expect(Gitlab::GitalyClient::HealthCheckService).to receive(:new).and_return(gitaly_check) + end + + context 'Gitaly server is up' do + let(:gitaly_check) { double(check: { success: true }) } + + it 'provides metrics' do + expect(subject).to all(have_attributes(labels: { shard: 'default' })) + expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_success', value: 1)) + expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_latency_seconds', value: be >= 0)) + end + end + + context 'Gitaly server is down' do + let(:gitaly_check) { double(check: { success: false, message: 'Connection refused' }) } + + it 'provides metrics' do + expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_success', value: 0)) + expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_latency_seconds', value: be >= 0)) + end + end + end +end diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb index aeacd577d18..506b2c0be20 100644 --- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -14,7 +14,6 @@ describe Gitlab::HookData::IssueBuilder do closed_at confidential created_at - deleted_at description due_date id diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb index 78475403f9e..b61614e4790 100644 --- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -12,7 +12,6 @@ describe Gitlab::HookData::MergeRequestBuilder do assignee_id author_id created_at - deleted_at description head_pipeline_id id diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb index 162b776e107..5cdc5138fda 100644 --- a/spec/lib/gitlab/import_export/file_importer_spec.rb +++ b/spec/lib/gitlab/import_export/file_importer_spec.rb @@ -12,30 +12,61 @@ describe Gitlab::ImportExport::FileImporter do stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true) - + allow(SecureRandom).to receive(:hex).and_return('abcd') setup_files - - described_class.import(archive_file: '', shared: shared) end after do FileUtils.rm_rf(export_path) end - it 'removes symlinks in root folder' do - expect(File.exist?(symlink_file)).to be false - end + context 'normal run' do + before do + described_class.import(archive_file: '', shared: shared) + end - it 'removes hidden symlinks in root folder' do - expect(File.exist?(hidden_symlink_file)).to be false - end + it 'removes symlinks in root folder' do + expect(File.exist?(symlink_file)).to be false + end + + it 'removes hidden symlinks in root folder' do + expect(File.exist?(hidden_symlink_file)).to be false + end + + it 'removes symlinks in subfolders' do + expect(File.exist?(subfolder_symlink_file)).to be false + end - it 'removes symlinks in subfolders' do - expect(File.exist?(subfolder_symlink_file)).to be false + it 'does not remove a valid file' do + expect(File.exist?(valid_file)).to be true + end + + it 'creates the file in the right subfolder' do + expect(shared.export_path).to include('test/abcd') + end end - it 'does not remove a valid file' do - expect(File.exist?(valid_file)).to be true + context 'error' do + before do + allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError) + described_class.import(archive_file: '', shared: shared) + end + + it 'removes symlinks in root folder' do + expect(File.exist?(symlink_file)).to be false + end + + it 'removes hidden symlinks in root folder' do + expect(File.exist?(hidden_symlink_file)).to be false + end + + it 'removes symlinks in subfolders' do + expect(File.exist?(subfolder_symlink_file)).to be false + end + + it 'does not remove a valid file' do + expect(File.exist?(valid_file)).to be true + end end def setup_files diff --git a/spec/lib/gitlab/import_export/project.group.json b/spec/lib/gitlab/import_export/project.group.json index 82a1fbd2fc5..1a561e81e4a 100644 --- a/spec/lib/gitlab/import_export/project.group.json +++ b/spec/lib/gitlab/import_export/project.group.json @@ -54,7 +54,6 @@ "iid": 1, "updated_by_id": 1, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "lock_version": null, @@ -134,7 +133,6 @@ "iid": 2, "updated_by_id": 1, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "lock_version": null, diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 7580b62cfc0..b6c1f0c81cb 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -56,7 +56,6 @@ "iid": 10, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "test_ee_field": "test", @@ -350,7 +349,6 @@ "iid": 9, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "milestone": { @@ -586,7 +584,6 @@ "iid": 8, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "label_links": [ @@ -820,7 +817,6 @@ "iid": 7, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1033,7 +1029,6 @@ "iid": 6, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1246,7 +1241,6 @@ "iid": 5, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1459,7 +1453,6 @@ "iid": 4, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1672,7 +1665,6 @@ "iid": 3, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1885,7 +1877,6 @@ "iid": 2, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -2098,7 +2089,6 @@ "iid": 1, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -2504,7 +2494,6 @@ "merge_when_pipeline_succeeds": true, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 671, @@ -2948,7 +2937,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 679, @@ -3228,7 +3216,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 777, @@ -3508,7 +3495,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 785, @@ -4198,7 +4184,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 793, @@ -4734,7 +4719,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 801, @@ -5223,7 +5207,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 809, @@ -5478,7 +5461,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 817, @@ -6168,7 +6150,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 825, @@ -6968,7 +6949,6 @@ "id": 123, "token": "cdbfasdf44a5958c83654733449e585", "project_id": 5, - "deleted_at": null, "created_at": "2017-01-16T15:25:28.637Z", "updated_at": "2017-01-16T15:25:28.637Z" } @@ -7116,7 +7096,7 @@ "project_id": 5, "created_at": "2016-06-14T15:01:51.232Z", "updated_at": "2016-06-14T15:01:51.232Z", - "active": false, + "active": true, "properties": { }, diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json index 02450478a77..5dbf0ed289b 100644 --- a/spec/lib/gitlab/import_export/project.light.json +++ b/spec/lib/gitlab/import_export/project.light.json @@ -54,7 +54,6 @@ "iid": 20, "updated_by_id": 1, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "lock_version": null, 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 08e5bbbd400..5804c45871e 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -164,6 +164,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService') end + it 'saves the properties for a service' do + expect(saved_project_json['services'].first['properties']).to eq('one' => 'value') + end + it 'has project feature' do project_feature = saved_project_json['project_feature'] expect(project_feature).not_to be_empty @@ -279,7 +283,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do commit_id: ci_build.pipeline.sha) create(:event, :created, target: milestone, project: project, author: user) - create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker') + create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' }) create(:project_custom_attribute, project: project) create(:project_custom_attribute, project: project) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 1a554bd1a4e..847c77e0e80 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -14,7 +14,6 @@ Issue: - iid - updated_by_id - confidential -- deleted_at - closed_at - due_date - moved_to_id @@ -159,7 +158,6 @@ MergeRequest: - merge_when_pipeline_succeeds - merge_user_id - merge_commit_sha -- deleted_at - in_progress_merge_commit_sha - lock_version - milestone_id @@ -180,6 +178,7 @@ MergeRequestDiff: - real_size - head_commit_sha - start_commit_sha +- commits_count MergeRequestDiffCommit: - merge_request_diff_id - relative_order @@ -295,7 +294,6 @@ Ci::Trigger: - id - token - project_id -- deleted_at - created_at - updated_at - owner_id @@ -311,7 +309,6 @@ Ci::PipelineSchedule: - project_id - owner_id - active -- deleted_at - created_at - updated_at Clusters::Cluster: diff --git a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb new file mode 100644 index 00000000000..6532579b1c9 --- /dev/null +++ b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Gitlab::InsecureKeyFingerprint do + let(:key) do + 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn' \ + '1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qk' \ + 'r8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMg' \ + 'Jw0=' + end + + let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" } + + describe "#fingerprint" do + it "generates the key's fingerprint" do + expect(described_class.new(key.split[1]).fingerprint).to eq(fingerprint) + end + end +end diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index 4afe48e72ad..63997a40d52 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -100,6 +100,25 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do is_expected.to eq(command) end end + + context 'when chart values file is present' do + let(:install_command) { described_class.new(prometheus.name, chart: prometheus.chart, chart_values_file: prometheus.chart_values_file) } + let(:command) do + <<~MSG.chomp + set -eo pipefail + apk add -U ca-certificates openssl >/dev/null + wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null + mv /tmp/linux-amd64/helm /usr/bin/ + + helm init --client-only >/dev/null + helm install #{prometheus.chart} --name #{prometheus.name} --namespace #{namespace.name} -f /data/helm/#{prometheus.name}/config/values.yaml >/dev/null + MSG + end + + it 'should return appropriate command' do + is_expected.to eq(command) + end + end end describe "#pod_name" do diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index 906b10b96d4..0b8e97b8948 100644 --- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb @@ -52,18 +52,20 @@ describe Gitlab::Kubernetes::Helm::Pod do it 'should include volumes for the container' do container = subject.generate.spec.containers.first - expect(container.volumeMounts.first['name']).to eq('config-volume') - expect(container.volumeMounts.first['mountPath']).to eq('/etc/config') + expect(container.volumeMounts.first['name']).to eq('configuration-volume') + expect(container.volumeMounts.first['mountPath']).to eq("/data/helm/#{app.name}/config") end it 'should include a volume inside the specification' do spec = subject.generate.spec - expect(spec.volumes.first['name']).to eq('config-volume') + expect(spec.volumes.first['name']).to eq('configuration-volume') end it 'should mount configMap specification in the volume' do spec = subject.generate.spec - expect(spec.volumes.first.configMap['name']).to eq('values-config') + expect(spec.volumes.first.configMap['name']).to eq('values-content-configuration') + expect(spec.volumes.first.configMap['items'].first['key']).to eq('values') + expect(spec.volumes.first.configMap['items'].first['path']).to eq('values.yaml') end end diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb index 41a9d1d9c90..d9379cfe674 100644 --- a/spec/lib/gitlab/metrics/method_call_spec.rb +++ b/spec/lib/gitlab/metrics/method_call_spec.rb @@ -5,6 +5,10 @@ describe Gitlab::Metrics::MethodCall do let(:method_call) { described_class.new('Foo#bar', :Foo, '#bar', transaction) } describe '#measure' do + after do + described_class.reload_metric!(:gitlab_method_call_duration_seconds) + end + it 'measures the performance of the supplied block' do method_call.measure { 'foo' } @@ -20,8 +24,6 @@ describe Gitlab::Metrics::MethodCall do context 'prometheus instrumentation is enabled' do before do - allow(Feature.get(:prometheus_metrics_method_instrumentation)).to receive(:enabled?).and_call_original - described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1 Feature.get(:prometheus_metrics_method_instrumentation).enable end @@ -31,30 +33,12 @@ describe Gitlab::Metrics::MethodCall do end end - it 'caches subsequent invocations of feature check' do - 10.times do - method_call.measure { 'foo' } - end - - expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).once - end - - it 'expires feature check cache after 1 minute' do - method_call.measure { 'foo' } - - Timecop.travel(1.minute.from_now) do - method_call.measure { 'foo' } - end - - Timecop.travel(1.minute.from_now + 1.second) do - method_call.measure { 'foo' } - end - - expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).twice + it 'metric is not a NullMetric' do + expect(described_class).not_to be_instance_of(Gitlab::Metrics::NullMetric) end it 'observes the performance of the supplied block' do - expect(described_class.call_duration_histogram) + expect(described_class.gitlab_method_call_duration_seconds) .to receive(:observe) .with({ module: :Foo, method: '#bar' }, be_a_kind_of(Numeric)) @@ -64,14 +48,12 @@ describe Gitlab::Metrics::MethodCall do context 'prometheus instrumentation is disabled' do before do - described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1 - Feature.get(:prometheus_metrics_method_instrumentation).disable end - it 'does not observe the performance' do - expect(described_class.call_duration_histogram) - .not_to receive(:observe) + it 'observes using NullMetric' do + expect(described_class.gitlab_method_call_duration_seconds).to be_instance_of(Gitlab::Metrics::NullMetric) + expect(described_class.gitlab_method_call_duration_seconds).to receive(:observe) method_call.measure { 'foo' } end @@ -81,12 +63,10 @@ describe Gitlab::Metrics::MethodCall do context 'when measurement is below threshold' do before do allow(method_call).to receive(:above_threshold?).and_return(false) - - Feature.get(:prometheus_metrics_method_instrumentation).enable end it 'does not observe the performance' do - expect(described_class.call_duration_histogram) + expect(described_class.gitlab_method_call_duration_seconds) .not_to receive(:observe) method_call.measure { 'foo' } @@ -96,7 +76,7 @@ describe Gitlab::Metrics::MethodCall do describe '#to_metric' do it 'returns a Metric instance' do - expect(method_call).to receive(:real_time).and_return(4.0001) + expect(method_call).to receive(:real_time).and_return(4.0001).twice expect(method_call).to receive(:cpu_time).and_return(3.0001) method_call.measure { 'foo' } diff --git a/spec/lib/gitlab/metrics/methods_spec.rb b/spec/lib/gitlab/metrics/methods_spec.rb new file mode 100644 index 00000000000..9d41ed2442b --- /dev/null +++ b/spec/lib/gitlab/metrics/methods_spec.rb @@ -0,0 +1,137 @@ +require 'spec_helper' + +describe Gitlab::Metrics::Methods do + subject { Class.new { include Gitlab::Metrics::Methods } } + + shared_context 'metric' do |metric_type, *args| + let(:docstring) { 'description' } + let(:metric_name) { :sample_metric } + + describe "#define_#{metric_type}" do + define_method(:call_define_metric_method) do |**args| + subject.__send__("define_#{metric_type}", metric_name, **args) + end + + context 'metrics access method not defined' do + it "defines metrics accessing method" do + expect(subject).not_to respond_to(metric_name) + + call_define_metric_method(docstring: docstring) + + expect(subject).to respond_to(metric_name) + end + end + + context 'metrics access method defined' do + before do + call_define_metric_method(docstring: docstring) + end + + it 'raises error when trying to redefine method' do + expect { call_define_metric_method(docstring: docstring) }.to raise_error(ArgumentError) + end + + context 'metric is not cached' do + it 'calls fetch_metric' do + expect(subject).to receive(:init_metric).with(metric_type, metric_name, docstring: docstring) + + subject.public_send(metric_name) + end + end + + context 'metric is cached' do + before do + subject.public_send(metric_name) + end + + it 'returns cached metric' do + expect(subject).not_to receive(:init_metric) + + subject.public_send(metric_name) + end + end + end + end + + describe "#fetch_#{metric_type}" do + let(:null_metric) { Gitlab::Metrics::NullMetric.instance } + + define_method(:call_fetch_metric_method) do |**args| + subject.__send__("fetch_#{metric_type}", metric_name, **args) + end + + context "when #{metric_type} is not cached" do + it 'initializes counter metric' do + allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric) + + call_fetch_metric_method(docstring: docstring) + + expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args) + end + end + + context "when #{metric_type} is cached" do + before do + call_fetch_metric_method(docstring: docstring) + end + + it 'uses class metric cache' do + expect(Gitlab::Metrics).not_to receive(metric_type) + + call_fetch_metric_method(docstring: docstring) + end + + context 'when metric is reloaded' do + before do + subject.reload_metric!(metric_name) + end + + it "initializes #{metric_type} metric" do + allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric) + + call_fetch_metric_method(docstring: docstring) + + expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args) + end + end + end + + context 'when metric is configured with feature' do + let(:feature_name) { :some_metric_feature } + let(:metric) { call_fetch_metric_method(docstring: docstring, with_feature: feature_name) } + + context 'when feature is enabled' do + before do + Feature.get(feature_name).enable + end + + it "initializes #{metric_type} metric" do + allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric) + + metric + + expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args) + end + end + + context 'when feature is disabled' do + before do + Feature.get(feature_name).disable + end + + it "returns NullMetric" do + allow(Gitlab::Metrics).to receive(metric_type) + + expect(metric).to be_instance_of(Gitlab::Metrics::NullMetric) + + expect(Gitlab::Metrics).not_to have_received(metric_type) + end + end + end + end + end + + include_examples 'metric', :counter, {} + include_examples 'metric', :gauge, {}, :all + include_examples 'metric', :histogram, {}, [0.005, 0.01, 0.1, 1, 10] +end diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb index 375cbf8a9ca..54781dd52fc 100644 --- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb @@ -2,6 +2,11 @@ require 'spec_helper' describe Gitlab::Metrics::Samplers::RubySampler do let(:sampler) { described_class.new(5) } + let(:null_metric) { double('null_metric', set: nil, observe: nil) } + + before do + allow(Gitlab::Metrics::NullMetric).to receive(:instance).and_return(null_metric) + end after do Allocations.stop if Gitlab::Metrics.mri? @@ -17,12 +22,9 @@ describe Gitlab::Metrics::Samplers::RubySampler do end it 'adds a metric containing the memory usage' do - expect(Gitlab::Metrics::System).to receive(:memory_usage) - .and_return(9000) + expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000) - expect(sampler.metrics[:memory_usage]).to receive(:set) - .with({}, 9000) - .and_call_original + expect(sampler.metrics[:memory_usage]).to receive(:set).with({}, 9000) sampler.sample end @@ -31,9 +33,7 @@ describe Gitlab::Metrics::Samplers::RubySampler do expect(Gitlab::Metrics::System).to receive(:file_descriptor_count) .and_return(4) - expect(sampler.metrics[:file_descriptors]).to receive(:set) - .with({}, 4) - .and_call_original + expect(sampler.metrics[:file_descriptors]).to receive(:set).with({}, 4) sampler.sample end @@ -49,16 +49,14 @@ describe Gitlab::Metrics::Samplers::RubySampler do it 'adds a metric containing garbage collection time statistics' do expect(GC::Profiler).to receive(:total_time).and_return(0.24) - expect(sampler.metrics[:total_time]).to receive(:set) - .with({}, 240) - .and_call_original + expect(sampler.metrics[:total_time]).to receive(:set).with({}, 240) sampler.sample end it 'adds a metric containing garbage collection statistics' do GC.stat.keys.each do |key| - expect(sampler.metrics[key]).to receive(:set).with({}, anything).and_call_original + expect(sampler.metrics[key]).to receive(:set).with({}, anything) end sampler.sample diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb index eca75a4fac1..9f3af1acef7 100644 --- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::Metrics::Subscribers::ActionView do end it 'observes view rendering time' do - expect(subscriber.send(:metric_view_rendering_duration_seconds)) + expect(described_class.gitlab_view_rendering_duration_seconds) .to receive(:observe) .with({ view: 'app/views/x.html.haml' }, 2.1) diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb index 9b3698fb4a8..4e7bd433a9c 100644 --- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb @@ -25,7 +25,7 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do expect(subscriber).to receive(:current_transaction) .at_least(:once) .and_return(transaction) - expect(subscriber.send(:metric_sql_duration_seconds)).to receive(:observe).with({}, 0.002) + expect(described_class.send(:gitlab_sql_duration_seconds)).to receive(:observe).with({}, 0.002) subscriber.sql(event) end diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb index 58e28592cf9..6795c1ab56b 100644 --- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb @@ -144,7 +144,10 @@ describe Gitlab::Metrics::Subscribers::RailsCache do end context 'with a transaction' do + let(:metric_cache_misses_total) { double('metric_cache_misses_total', increment: nil) } + before do + allow(subscriber).to receive(:metric_cache_misses_total).and_return(metric_cache_misses_total) allow(subscriber).to receive(:current_transaction) .and_return(transaction) end @@ -157,9 +160,9 @@ describe Gitlab::Metrics::Subscribers::RailsCache do end it 'increments the cache_read_miss total' do - expect(subscriber.send(:metric_cache_misses_total)).to receive(:increment).with({}) - subscriber.cache_generate(event) + + expect(metric_cache_misses_total).to have_received(:increment).with({}) end end end diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index 1619fbd88b1..9e405e9f736 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::Metrics do context 'prometheus metrics enabled in config' do before do - allow(described_class).to receive(:current_application_settings).and_return(prometheus_metrics_enabled: true) + allow(Gitlab::CurrentSettings).to receive(:current_application_settings).and_return(prometheus_metrics_enabled: true) end context 'when metrics folder is present' do diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 45fff4c5787..03e0a9e2a03 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -44,6 +44,18 @@ describe Gitlab::OAuth::User do let(:provider) { 'twitter' } + describe 'when account exists on server' do + it 'does not mark the user as external' do + create(:omniauth_user, extern_uid: 'my-uid', provider: provider) + stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) + + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.external).to be_falsey + end + end + describe 'signup' do context 'when signup is disabled' do before do @@ -51,7 +63,7 @@ describe Gitlab::OAuth::User do end it 'creates the user' do - stub_omniauth_config(allow_single_sign_on: ['twitter']) + stub_omniauth_config(allow_single_sign_on: [provider]) oauth_user.save @@ -65,7 +77,7 @@ describe Gitlab::OAuth::User do end it 'creates and confirms the user anyway' do - stub_omniauth_config(allow_single_sign_on: ['twitter']) + stub_omniauth_config(allow_single_sign_on: [provider]) oauth_user.save @@ -75,7 +87,7 @@ describe Gitlab::OAuth::User do end it 'marks user as having password_automatically_set' do - stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['twitter']) + stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) oauth_user.save @@ -86,7 +98,7 @@ describe Gitlab::OAuth::User do shared_examples 'to verify compliance with allow_single_sign_on' do context 'provider is marked as external' do it 'marks user as external' do - stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['twitter']) + stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider]) oauth_user.save expect(gl_user).to be_valid expect(gl_user.external).to be_truthy @@ -95,8 +107,8 @@ describe Gitlab::OAuth::User do context 'provider was external, now has been removed' do it 'does not mark external user as internal' do - create(:omniauth_user, extern_uid: 'my-uid', provider: 'twitter', external: true) - stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['facebook']) + 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 expect(gl_user).to be_valid expect(gl_user.external).to be_truthy @@ -118,7 +130,7 @@ describe Gitlab::OAuth::User do context 'with new allow_single_sign_on enabled syntax' do before do - stub_omniauth_config(allow_single_sign_on: ['twitter']) + stub_omniauth_config(allow_single_sign_on: [provider]) end it "creates a user from Omniauth" do @@ -127,7 +139,7 @@ describe Gitlab::OAuth::User do expect(gl_user).to be_valid identity = gl_user.identities.first expect(identity.extern_uid).to eql uid - expect(identity.provider).to eql 'twitter' + expect(identity.provider).to eql provider end end @@ -142,7 +154,7 @@ describe Gitlab::OAuth::User do expect(gl_user).to be_valid identity = gl_user.identities.first expect(identity.extern_uid).to eql uid - expect(identity.provider).to eql 'twitter' + expect(identity.provider).to eql provider end end diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 0ae90069b7f..85991c38363 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -121,7 +121,7 @@ describe Gitlab::PathRegex do STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id} NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*} ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*} - WILDCARD_SEGMENT = %r{\*} + WILDCARD_SEGMENT = /\*/ let(:namespaced_wildcard_routes) do routes_without_format.select do |p| p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}} diff --git a/spec/lib/gitlab/popen/runner_spec.rb b/spec/lib/gitlab/popen/runner_spec.rb new file mode 100644 index 00000000000..2e2cb4ca28f --- /dev/null +++ b/spec/lib/gitlab/popen/runner_spec.rb @@ -0,0 +1,139 @@ +require 'spec_helper' + +describe Gitlab::Popen::Runner do + subject { described_class.new } + + describe '#run' do + it 'runs the command and returns the result' do + run_command + + expect(Gitlab::Popen).to have_received(:popen_with_detail) + end + end + + describe '#all_success_and_clean?' do + it 'returns true when exit status is 0 and stderr is empty' do + run_command + + expect(subject).to be_all_success_and_clean + end + + it 'returns false when exit status is not 0' do + run_command(exitstatus: 1) + + expect(subject).not_to be_all_success_and_clean + end + + it 'returns false when exit stderr has something' do + run_command(stderr: 'stderr') + + expect(subject).not_to be_all_success_and_clean + end + end + + describe '#all_success?' do + it 'returns true when exit status is 0' do + run_command + + expect(subject).to be_all_success + end + + it 'returns false when exit status is not 0' do + run_command(exitstatus: 1) + + expect(subject).not_to be_all_success + end + + it 'returns true' do + run_command(stderr: 'stderr') + + expect(subject).to be_all_success + end + end + + describe '#all_stderr_empty?' do + it 'returns true when stderr is empty' do + run_command + + expect(subject).to be_all_stderr_empty + end + + it 'returns true when exit status is not 0' do + run_command(exitstatus: 1) + + expect(subject).to be_all_stderr_empty + end + + it 'returns false when exit stderr has something' do + run_command(stderr: 'stderr') + + expect(subject).not_to be_all_stderr_empty + end + end + + describe '#failed_results' do + it 'returns [] when everything is passed' do + run_command + + expect(subject.failed_results).to be_empty + end + + it 'returns the result when exit status is not 0' do + result = run_command(exitstatus: 1) + + expect(subject.failed_results).to contain_exactly(result) + end + + it 'returns [] when exit stderr has something' do + run_command(stderr: 'stderr') + + expect(subject.failed_results).to be_empty + end + end + + describe '#warned_results' do + it 'returns [] when everything is passed' do + run_command + + expect(subject.warned_results).to be_empty + end + + it 'returns [] when exit status is not 0' do + run_command(exitstatus: 1) + + expect(subject.warned_results).to be_empty + end + + it 'returns the result when exit stderr has something' do + result = run_command(stderr: 'stderr') + + expect(subject.warned_results).to contain_exactly(result) + end + end + + def run_command( + command: 'command', + stdout: 'stdout', + stderr: '', + exitstatus: 0, + status: double(exitstatus: exitstatus, success?: exitstatus.zero?), + duration: 0.1) + + result = + Gitlab::Popen::Result.new(command, stdout, stderr, status, duration) + + allow(Gitlab::Popen) + .to receive(:popen_with_detail) + .and_return(result) + + subject.run([command]) do |cmd, &run| + expect(cmd).to eq(command) + + cmd_result = run.call + + expect(cmd_result).to eq(result) + end + + subject.results.first + end +end diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index b145ca36f26..1dbead16d5b 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -1,11 +1,23 @@ require 'spec_helper' -describe 'Gitlab::Popen' do +describe Gitlab::Popen do let(:path) { Rails.root.join('tmp').to_s } before do @klass = Class.new(Object) - @klass.send(:include, Gitlab::Popen) + @klass.send(:include, described_class) + end + + describe '.popen_with_detail' do + subject { @klass.new.popen_with_detail(cmd) } + + let(:cmd) { %W[#{Gem.ruby} -e $stdout.puts(1);$stderr.puts(2);exit(3)] } + + it { expect(subject.cmd).to eq(cmd) } + it { expect(subject.stdout).to eq("1\n") } + it { expect(subject.stderr).to eq("2\n") } + it { expect(subject.status.exitstatus).to eq(3) } + it { expect(subject.duration).to be_kind_of(Numeric) } end context 'zero status' do diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb new file mode 100644 index 00000000000..4a43dbb2371 --- /dev/null +++ b/spec/lib/gitlab/profiler_spec.rb @@ -0,0 +1,156 @@ +require 'spec_helper' + +describe Gitlab::Profiler do + RSpec::Matchers.define_negated_matcher :not_change, :change + + let(:null_logger) { Logger.new('/dev/null') } + let(:private_token) { 'private' } + + describe '.profile' do + let(:app) { double(:app) } + + before do + allow(ActionDispatch::Integration::Session).to receive(:new).and_return(app) + allow(app).to receive(:get) + end + + it 'returns a profile result' do + expect(described_class.profile('/')).to be_an_instance_of(RubyProf::Profile) + end + + it 'uses the custom logger given' do + expect(described_class).to receive(:create_custom_logger) + .with(null_logger, private_token: anything) + .and_call_original + + described_class.profile('/', logger: null_logger) + end + + it 'sends a POST request when data is passed' do + post_data = '{"a":1}' + + expect(app).to receive(:post).with(anything, post_data, anything) + + described_class.profile('/', post_data: post_data) + end + + it 'uses the private_token for auth if given' do + expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token) + expect(app).to receive(:get).with('/api/v4/users') + + described_class.profile('/', private_token: private_token) + end + + it 'uses the user for auth if given' do + user = double(:user) + user_token = 'user' + + allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck, :first).and_return(user_token) + + expect(app).to receive(:get).with('/', nil, 'Private-Token' => user_token) + expect(app).to receive(:get).with('/api/v4/users') + + described_class.profile('/', user: user) + end + + it 'uses the private_token for auth if both it and user are set' do + user = double(:user) + user_token = 'user' + + allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck, :first).and_return(user_token) + + expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token) + expect(app).to receive(:get).with('/api/v4/users') + + described_class.profile('/', user: user, private_token: private_token) + end + end + + describe '.create_custom_logger' do + it 'does nothing when nil is passed' do + expect(described_class.create_custom_logger(nil)).to be_nil + end + + context 'the new logger' do + let(:custom_logger) do + described_class.create_custom_logger(null_logger, private_token: private_token) + end + + it 'does not affect the existing logger' do + expect(null_logger).not_to receive(:debug) + expect(custom_logger).to receive(:debug).and_call_original + + custom_logger.debug('Foo') + end + + it 'strips out the private token' do + expect(custom_logger).to receive(:add) do |severity, _progname, message| + expect(severity).to eq(Logger::DEBUG) + expect(message).to include('public').and include(described_class::FILTERED_STRING) + expect(message).not_to include(private_token) + end + + custom_logger.debug("public #{private_token}") + end + + it 'tracks model load times by model' do + custom_logger.debug('This is not a model load') + custom_logger.debug('User Load (1.2ms)') + custom_logger.debug('User Load (1.3ms)') + custom_logger.debug('Project Load (10.4ms)') + + expect(custom_logger.load_times_by_model).to eq('User' => 2.5, + 'Project' => 10.4) + end + + it 'logs the backtrace, ignoring lines as appropriate' do + # Skip Rails's backtrace cleaning. + allow(Rails.backtrace_cleaner).to receive(:clean, &:itself) + + expect(custom_logger).to receive(:add) + .with(Logger::DEBUG, + anything, + a_string_matching(File.basename(__FILE__))) + .twice + + expect(custom_logger).not_to receive(:add).with(Logger::DEBUG, + anything, + a_string_matching('lib/gitlab/profiler.rb')) + + # Force a part of the backtrace to be in the (ignored) profiler source + # file. + described_class.with_custom_logger(nil) { custom_logger.debug('Foo') } + end + end + end + + describe '.with_custom_logger' do + context 'when the logger is set' do + it 'uses the replacement logger for the duration of the block' do + expect(null_logger).to receive(:debug).and_call_original + + expect { described_class.with_custom_logger(null_logger) { ActiveRecord::Base.logger.debug('foo') } } + .to not_change { ActiveRecord::Base.logger } + .and not_change { ActionController::Base.logger } + .and not_change { ActiveSupport::LogSubscriber.colorize_logging } + end + + it 'returns the result of the block' do + expect(described_class.with_custom_logger(null_logger) { 2 }).to eq(2) + end + end + + context 'when the logger is nil' do + it 'returns the result of the block' do + expect(described_class.with_custom_logger(nil) { 2 }).to eq(2) + end + + it 'does not modify the standard Rails loggers' do + expect { described_class.with_custom_logger(nil) { } } + .to not_change { ActiveRecord::Base.logger } + .and not_change { ActionController::Base.logger } + .and not_change { ActiveSupport::LogSubscriber.colorize_logging } + end + end + end +end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index 17937726f2c..1ebb0105cf5 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -70,15 +70,6 @@ describe Gitlab::ProjectSearchResults do subject { described_class.parse_search_result(search_result) } - it 'can correctly parse filenames including ":"' do - special_char_result = "\nmaster:testdata/project::function1.yaml-1----\nmaster:testdata/project::function1.yaml:2:test: data1\n" - - blob = described_class.parse_search_result(special_char_result) - - expect(blob.ref).to eq('master') - expect(blob.filename).to eq('testdata/project::function1.yaml') - end - it "returns a valid FoundBlob" do is_expected.to be_an Gitlab::SearchResults::FoundBlob expect(subject.id).to be_nil @@ -90,8 +81,32 @@ describe Gitlab::ProjectSearchResults do expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") end + context 'when the matching filename contains a colon' do + let(:search_result) { "\nmaster:testdata/project::function1.yaml\x001\x00---\n" } + + it 'returns a valid FoundBlob' do + expect(subject.filename).to eq('testdata/project::function1.yaml') + expect(subject.basename).to eq('testdata/project::function1') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq('---') + end + end + + context 'when the matching content contains a number surrounded by colons' do + let(:search_result) { "\nmaster:testdata/foo.txt\x001\x00blah:9:blah" } + + it 'returns a valid FoundBlob' do + expect(subject.filename).to eq('testdata/foo.txt') + expect(subject.basename).to eq('testdata/foo') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq('blah:9:blah') + end + end + context "when filename has extension" do - let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" } + let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" } it { expect(subject.path).to eq('CONTRIBUTE.md') } it { expect(subject.filename).to eq('CONTRIBUTE.md') } @@ -99,7 +114,7 @@ describe Gitlab::ProjectSearchResults do end context "when file under directory" do - let(:search_result) { "master:a/b/c.md:5:a b c\n" } + let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" } it { expect(subject.path).to eq('a/b/c.md') } it { expect(subject.filename).to eq('a/b/c.md') } @@ -144,7 +159,7 @@ describe Gitlab::ProjectSearchResults do end it 'finds by content' do - expect(results).to include("master:Title.md:1:Content\n") + expect(results).to include("master:Title.md\x001\x00Content\n") end end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 68a57826647..8b54d72d6f7 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Regex do it { is_expected.not_to match('?gitlab') } end - describe '.environment_slug_regex' do + describe '.environment_name_regex' do subject { described_class.environment_name_regex } it { is_expected.to match('foo') } @@ -24,6 +24,7 @@ describe Gitlab::Regex do it { is_expected.to match('foo.1') } it { is_expected.not_to match('9&foo') } it { is_expected.not_to match('foo-^') } + it { is_expected.not_to match('!!()()') } end describe '.environment_slug_regex' do diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index b5a9ac570e6..17b48b3d062 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -19,6 +19,12 @@ describe Gitlab::SearchResults do project.add_developer(user) end + describe '#objects' do + it 'returns without_page collection by default' do + expect(results.objects('projects')).to be_kind_of(Kaminari::PaginatableWithoutCount) + end + end + describe '#projects_count' do it 'returns the total amount of projects' do expect(results.projects_count).to eq(1) @@ -43,6 +49,58 @@ describe Gitlab::SearchResults do end end + context "when count_limit is lower than total amount" do + before do + allow(results).to receive(:count_limit).and_return(1) + end + + describe '#limited_projects_count' do + it 'returns the limited amount of projects' do + create(:project, name: 'foo2') + + expect(results.limited_projects_count).to eq(1) + end + end + + describe '#limited_merge_requests_count' do + it 'returns the limited amount of merge requests' do + create(:merge_request, :simple, source_project: project, title: 'foo2') + + expect(results.limited_merge_requests_count).to eq(1) + end + end + + describe '#limited_milestones_count' do + it 'returns the limited amount of milestones' do + create(:milestone, project: project, title: 'foo2') + + expect(results.limited_milestones_count).to eq(1) + end + end + + describe '#limited_issues_count' do + it 'runs single SQL query to get the limited amount of issues' do + create(:milestone, project: project, title: 'foo2') + + expect(results).to receive(:issues).with(public_only: true).and_call_original + expect(results).not_to receive(:issues).with(no_args).and_call_original + + expect(results.limited_issues_count).to eq(1) + end + end + end + + context "when count_limit is higher than total amount" do + describe '#limited_issues_count' do + it 'runs multiple queries to get the limited amount of issues' do + expect(results).to receive(:issues).with(public_only: true).and_call_original + expect(results).to receive(:issues).with(no_args).and_call_original + + expect(results.limited_issues_count).to eq(1) + end + end + end + it 'includes merge requests from source and target projects' do forked_project = fork_project(project, user) merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo') diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 81d9e6a8f82..2b61ce38418 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -4,6 +4,7 @@ require 'stringio' describe Gitlab::Shell do set(:project) { create(:project, :repository) } + let(:repository) { project.repository } let(:gitlab_shell) { described_class.new } let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } } let(:gitlab_projects) { double('gitlab_projects') } @@ -51,6 +52,311 @@ describe Gitlab::Shell do end end + describe '#add_key' do + context 'when authorized_keys_enabled is true' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( + [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end + end + + context 'when authorized_keys_enabled is false' do + before do + stub_application_setting(authorized_keys_enabled: false) + end + + it 'does nothing' do + expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute) + + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end + end + + context 'when authorized_keys_enabled is nil' do + before do + stub_application_setting(authorized_keys_enabled: nil) + end + + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( + [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end + end + end + + describe '#batch_add_keys' do + context 'when authorized_keys_enabled is true' do + it 'instantiates KeyAdder' do + expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar') + + gitlab_shell.batch_add_keys do |adder| + adder.add_key('key-123', 'ssh-rsa foobar') + end + end + end + + context 'when authorized_keys_enabled is false' do + before do + stub_application_setting(authorized_keys_enabled: false) + end + + it 'does nothing' do + expect_any_instance_of(Gitlab::Shell::KeyAdder).not_to receive(:add_key) + + gitlab_shell.batch_add_keys do |adder| + adder.add_key('key-123', 'ssh-rsa foobar') + end + end + end + + context 'when authorized_keys_enabled is nil' do + before do + stub_application_setting(authorized_keys_enabled: nil) + end + + it 'instantiates KeyAdder' do + expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar') + + gitlab_shell.batch_add_keys do |adder| + adder.add_key('key-123', 'ssh-rsa foobar') + end + end + end + end + + describe '#remove_key' do + context 'when authorized_keys_enabled is true' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( + [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.remove_key('key-123', 'ssh-rsa foobar') + end + end + + context 'when authorized_keys_enabled is false' do + before do + stub_application_setting(authorized_keys_enabled: false) + end + + it 'does nothing' do + expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute) + + gitlab_shell.remove_key('key-123', 'ssh-rsa foobar') + end + end + + context 'when authorized_keys_enabled is nil' do + before do + stub_application_setting(authorized_keys_enabled: nil) + end + + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( + [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.remove_key('key-123', 'ssh-rsa foobar') + end + end + + context 'when key content is not given' do + it 'calls rm-key with only one argument' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( + [:gitlab_shell_keys_path, 'rm-key', 'key-123'] + ) + + gitlab_shell.remove_key('key-123') + end + end + end + + describe '#remove_all_keys' do + context 'when authorized_keys_enabled is true' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with([:gitlab_shell_keys_path, 'clear']) + + gitlab_shell.remove_all_keys + end + end + + context 'when authorized_keys_enabled is false' do + before do + stub_application_setting(authorized_keys_enabled: false) + end + + it 'does nothing' do + expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute) + + gitlab_shell.remove_all_keys + end + end + + context 'when authorized_keys_enabled is nil' do + before do + stub_application_setting(authorized_keys_enabled: nil) + end + + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( + [:gitlab_shell_keys_path, 'clear'] + ) + + gitlab_shell.remove_all_keys + end + end + end + + describe '#remove_keys_not_found_in_db' do + context 'when keys are in the file that are not in the DB' do + before do + gitlab_shell.remove_all_keys + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF') + @another_key = create(:key) # this one IS in the DB + end + + it 'removes the keys' do + expect(find_in_authorized_keys_file(1234)).to be_truthy + expect(find_in_authorized_keys_file(9876)).to be_truthy + expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy + gitlab_shell.remove_keys_not_found_in_db + expect(find_in_authorized_keys_file(1234)).to be_falsey + expect(find_in_authorized_keys_file(9876)).to be_falsey + expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy + end + end + + context 'when keys there are duplicate keys in the file that are not in the DB' do + before do + gitlab_shell.remove_all_keys + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + end + + it 'removes the keys' do + expect(find_in_authorized_keys_file(1234)).to be_truthy + gitlab_shell.remove_keys_not_found_in_db + expect(find_in_authorized_keys_file(1234)).to be_falsey + end + + it 'does not run remove more than once per key (in a batch)' do + expect(gitlab_shell).to receive(:remove_key).with('key-1234').once + gitlab_shell.remove_keys_not_found_in_db + end + end + + context 'when keys there are duplicate keys in the file that ARE in the DB' do + before do + gitlab_shell.remove_all_keys + @key = create(:key) + gitlab_shell.add_key(@key.shell_id, @key.key) + end + + it 'does not remove the key' do + gitlab_shell.remove_keys_not_found_in_db + expect(find_in_authorized_keys_file(@key.id)).to be_truthy + end + + it 'does not need to run a SELECT query for that batch, on account of that key' do + expect_any_instance_of(ActiveRecord::Relation).not_to receive(:pluck) + gitlab_shell.remove_keys_not_found_in_db + end + end + + unless ENV['CI'] # Skip in CI, it takes 1 minute + context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do + before do + gitlab_shell.remove_all_keys + 100.times { |i| create(:key) } # first batch is all in the DB + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + end + + it 'removes the keys not in the DB' do + expect(find_in_authorized_keys_file(1234)).to be_truthy + gitlab_shell.remove_keys_not_found_in_db + expect(find_in_authorized_keys_file(1234)).to be_falsey + end + end + end + end + + describe '#batch_read_key_ids' do + context 'when there are keys in the authorized_keys file' do + before do + gitlab_shell.remove_all_keys + (1..4).each do |i| + gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}") + end + end + + it 'iterates over the key IDs in the file, in batches' do + loop_count = 0 + first_batch = [1, 2] + second_batch = [3, 4] + + gitlab_shell.batch_read_key_ids(batch_size: 2) do |batch| + expected = (loop_count == 0 ? first_batch : second_batch) + expect(batch).to eq(expected) + loop_count += 1 + end + end + end + end + + describe '#list_key_ids' do + context 'when there are keys in the authorized_keys file' do + before do + gitlab_shell.remove_all_keys + (1..4).each do |i| + gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}") + end + end + + it 'outputs the key IDs in the file, separated by newlines' do + ids = [] + gitlab_shell.list_key_ids do |io| + io.each do |line| + ids << line + end + end + + expect(ids).to eq(%W{1\n 2\n 3\n 4\n}) + end + end + + context 'when there are no keys in the authorized_keys file' do + before do + gitlab_shell.remove_all_keys + end + + it 'outputs nothing, not even an empty string' do + ids = [] + gitlab_shell.list_key_ids do |io| + io.each do |line| + ids << line + end + end + + expect(ids).to eq([]) + end + end + end + describe Gitlab::Shell::KeyAdder do describe '#add_key' do it 'removes trailing garbage' do @@ -96,17 +402,6 @@ describe Gitlab::Shell do allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800) end - describe '#add_key' do - it 'removes trailing garbage' do - allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) - expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( - [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] - ) - - gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') - end - end - describe '#add_repository' do shared_examples '#add_repository' do let(:repository_storage) { 'default' } @@ -148,32 +443,44 @@ describe Gitlab::Shell do end describe '#remove_repository' do - subject { gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path) } + let!(:project) { create(:project, :repository) } + let(:disk_path) { "#{project.disk_path}.git" } it 'returns true when the command succeeds' do - expect(gitlab_projects).to receive(:rm_project) { true } + expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(true) - is_expected.to be_truthy + expect(gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path)).to be(true) + + expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false) end - it 'returns false when the command fails' do - expect(gitlab_projects).to receive(:rm_project) { false } + it 'keeps the namespace directory' do + gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path) - is_expected.to be_falsy + expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false) + expect(gitlab_shell.exists?(project.repository_storage_path, project.disk_path.gsub(project.name, ''))).to be(true) end end describe '#mv_repository' do + let!(:project2) { create(:project, :repository) } + it 'returns true when the command succeeds' do - expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { true } + old_path = project2.disk_path + new_path = "project/new_path" + + expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(true) + expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(false) - expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_truthy + expect(gitlab_shell.mv_repository(project2.repository_storage_path, old_path, new_path)).to be_truthy + + expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(false) + expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(true) end it 'returns false when the command fails' do - expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { false } - - expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_falsy + expect(gitlab_shell.mv_repository(project2.repository_storage_path, project2.disk_path, '')).to be_falsy + expect(gitlab_shell.exists?(project2.repository_storage_path, "#{project2.disk_path}.git")).to be(true) end end @@ -201,8 +508,6 @@ describe Gitlab::Shell do end shared_examples 'fetch_remote' do |gitaly_on| - let(:repository) { project.repository } - def fetch_remote(ssh_auth = nil) gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', ssh_auth: ssh_auth) end @@ -325,6 +630,23 @@ describe Gitlab::Shell do describe '#fetch_remote gitaly' do it_should_behave_like 'fetch_remote', true + + context 'gitaly call' do + let(:remote_name) { 'remote-name' } + let(:ssh_auth) { double(:ssh_auth) } + + subject do + gitlab_shell.fetch_remote(repository.raw_repository, remote_name, + forced: true, no_tags: true, ssh_auth: ssh_auth) + end + + it 'passes the correct params to the gitaly service' do + expect(repository.gitaly_repository_client).to receive(:fetch_remote) + .with(remote_name, ssh_auth: ssh_auth, forced: true, no_tags: true, timeout: timeout) + + subject + end + end end describe '#import_repository' do @@ -396,4 +718,12 @@ describe Gitlab::Shell do end end end + + def find_in_authorized_keys_file(key_id) + gitlab_shell.batch_read_key_ids do |ids| + return true if ids.include?(key_id) + end + + false + end end diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb index e41e5254dde..35d01efc1bd 100644 --- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::SlashCommands::IssueSearch do let!(:issue) { create(:issue, project: project, title: 'find me') } let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') } let(:project) { create(:project) } - let(:user) { issue.author } + let(:user) { create(:user) } let(:regex_match) { described_class.match("issue search find") } subject do diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb new file mode 100644 index 00000000000..7c97cee982a --- /dev/null +++ b/spec/lib/gitlab/utils/override_spec.rb @@ -0,0 +1,158 @@ +require 'spec_helper' + +describe Gitlab::Utils::Override do + let(:base) { Struct.new(:good) } + + let(:derived) { Class.new(base).tap { |m| m.extend described_class } } + let(:extension) { Module.new.tap { |m| m.extend described_class } } + + let(:prepending_class) { base.tap { |m| m.prepend extension } } + let(:including_class) { base.tap { |m| m.include extension } } + + let(:klass) { subject } + + def good(mod) + mod.module_eval do + override :good + def good + super.succ + end + end + + mod + end + + def bad(mod) + mod.module_eval do + override :bad + def bad + true + end + end + + mod + end + + shared_examples 'checking as intended' do + it 'checks ok for overriding method' do + good(subject) + result = klass.new(0).good + + expect(result).to eq(1) + described_class.verify! + end + + it 'raises NotImplementedError when it is not overriding anything' do + expect do + bad(subject) + klass.new(0).bad + described_class.verify! + end.to raise_error(NotImplementedError) + end + end + + shared_examples 'nothing happened' do + it 'does not complain when it is overriding something' do + good(subject) + result = klass.new(0).good + + expect(result).to eq(1) + described_class.verify! + end + + it 'does not complain when it is not overriding anything' do + bad(subject) + result = klass.new(0).bad + + expect(result).to eq(true) + described_class.verify! + end + end + + before do + # Make sure we're not touching the internal cache + allow(described_class).to receive(:extensions).and_return({}) + end + + describe '#override' do + context 'when STATIC_VERIFICATION is set' do + before do + stub_env('STATIC_VERIFICATION', 'true') + end + + context 'when subject is a class' do + subject { derived } + + it_behaves_like 'checking as intended' + end + + context 'when subject is a module, and class is prepending it' do + subject { extension } + let(:klass) { prepending_class } + + it_behaves_like 'checking as intended' + end + + context 'when subject is a module, and class is including it' do + subject { extension } + let(:klass) { including_class } + + it 'raises NotImplementedError because it is not overriding it' do + expect do + good(subject) + klass.new(0).good + described_class.verify! + end.to raise_error(NotImplementedError) + end + + it 'raises NotImplementedError when it is not overriding anything' do + expect do + bad(subject) + klass.new(0).bad + described_class.verify! + end.to raise_error(NotImplementedError) + end + end + end + end + + context 'when STATIC_VERIFICATION is not set' do + before do + stub_env('STATIC_VERIFICATION', nil) + end + + context 'when subject is a class' do + subject { derived } + + it_behaves_like 'nothing happened' + end + + context 'when subject is a module, and class is prepending it' do + subject { extension } + let(:klass) { prepending_class } + + it_behaves_like 'nothing happened' + end + + context 'when subject is a module, and class is including it' do + subject { extension } + let(:klass) { including_class } + + it 'does not complain when it is overriding something' do + good(subject) + result = klass.new(0).good + + expect(result).to eq(0) + described_class.verify! + end + + it 'does not complain when it is not overriding anything' do + bad(subject) + result = klass.new(0).bad + + expect(result).to eq(true) + described_class.verify! + end + end + end +end diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb index e872a5290c5..bda239b7871 100644 --- a/spec/lib/gitlab/utils_spec.rb +++ b/spec/lib/gitlab/utils_spec.rb @@ -17,6 +17,22 @@ describe Gitlab::Utils do end end + describe '.remove_line_breaks' do + using RSpec::Parameterized::TableSyntax + + where(:original, :expected) do + "foo\nbar\nbaz" | "foobarbaz" + "foo\r\nbar\r\nbaz" | "foobarbaz" + "foobar" | "foobar" + end + + with_them do + it "replace line breaks with an empty string" do + expect(described_class.remove_line_breaks(original)).to eq(expected) + end + end + end + describe '.to_boolean' do it 'accepts booleans' do expect(to_boolean(true)).to be(true) diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 0b34d71bfb2..dc2bb5b9747 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -26,11 +26,16 @@ describe Gitlab::Workhorse do 'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys ) end + let(:cache_disabled) { false } subject do described_class.send_git_archive(repository, ref: ref, format: format) end + before do + allow(described_class).to receive(:git_archive_cache_disabled?).and_return(cache_disabled) + end + context 'when Gitaly workhorse_archive feature is enabled' do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) @@ -39,6 +44,15 @@ describe Gitlab::Workhorse do expect(command).to eq('git-archive') expect(params).to include(gitaly_params) end + + context 'when archive caching is disabled' do + let(:cache_disabled) { true } + + it 'tells workhorse not to use the cache' do + _, _, params = decode_workhorse_header(subject) + expect(params).to include({ 'DisableCache' => true }) + end + end end context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do |