diff options
Diffstat (limited to 'spec/lib')
68 files changed, 2454 insertions, 921 deletions
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb new file mode 100644 index 00000000000..14d055cbcc1 --- /dev/null +++ b/spec/lib/backup/files_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Backup::Files do + let(:progress) { StringIO.new } + let!(:project) { create(:project) } + + before do + allow(progress).to receive(:puts) + allow(progress).to receive(:print) + allow(FileUtils).to receive(:mkdir_p).and_return(true) + allow(FileUtils).to receive(:mv).and_return(true) + allow(File).to receive(:exist?).and_return(true) + allow(File).to receive(:realpath).with("/var/gitlab-registry").and_return("/var/gitlab-registry") + allow(File).to receive(:realpath).with("/var/gitlab-registry/..").and_return("/var") + + allow_any_instance_of(String).to receive(:color) do |string, _color| + string + end + + allow_any_instance_of(described_class).to receive(:progress).and_return(progress) + end + + describe '#restore' do + subject { described_class.new('registry', '/var/gitlab-registry') } + let(:timestamp) { Time.utc(2017, 3, 22) } + + around do |example| + Timecop.freeze(timestamp) { example.run } + end + + describe 'folders with permission' do + before do + allow(subject).to receive(:run_pipeline!).and_return(true) + allow(subject).to receive(:backup_existing_files).and_return(true) + allow(Dir).to receive(:glob).with("/var/gitlab-registry/*", File::FNM_DOTMATCH).and_return(["/var/gitlab-registry/.", "/var/gitlab-registry/..", "/var/gitlab-registry/sample1"]) + end + + it 'moves all necessary files' do + allow(subject).to receive(:backup_existing_files).and_call_original + expect(FileUtils).to receive(:mv).with(["/var/gitlab-registry/sample1"], File.join(Gitlab.config.backup.path, "tmp", "registry.#{Time.now.to_i}")) + subject.restore + end + + it 'raises no errors' do + expect { subject.restore }.not_to raise_error + end + + it 'calls tar command with unlink' do + expect(subject).to receive(:run_pipeline!).with([%w(gzip -cd), %w(tar --unlink-first --recursive-unlink -C /var/gitlab-registry -xf -)], any_args) + subject.restore + end + end + + describe 'folders without permissions' do + before do + allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES) + allow(subject).to receive(:run_pipeline!).and_return(true) + end + + it 'shows error message' do + expect(subject).to receive(:access_denied_error).with("/var/gitlab-registry") + subject.restore + end + end + end +end diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index 5100f5737c2..84688845fa5 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -278,6 +278,10 @@ describe Backup::Manager do connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) end + after do + Fog.unmock! + end + context 'target path' do it 'uses the tar filename by default' do expect_any_instance_of(Fog::Collection).to receive(:create) diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index a9b5ed1112a..e4c1c9bafc0 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -7,6 +7,8 @@ describe Backup::Repository do before do allow(progress).to receive(:puts) allow(progress).to receive(:print) + allow(FileUtils).to receive(:mkdir_p).and_return(true) + allow(FileUtils).to receive(:mv).and_return(true) allow_any_instance_of(String).to receive(:color) do |string, _color| string @@ -33,7 +35,7 @@ describe Backup::Repository do let(:timestamp) { Time.utc(2017, 3, 22) } let(:temp_dirs) do Gitlab.config.repositories.storages.map do |name, storage| - File.join(storage['path'], '..', 'repositories.old.' + timestamp.to_i.to_s) + File.join(storage.legacy_disk_path, '..', 'repositories.old.' + timestamp.to_i.to_s) end end @@ -68,6 +70,17 @@ describe Backup::Repository do end end end + + describe 'folders without permissions' do + before do + allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES) + end + + it 'shows error message' do + expect(subject).to receive(:access_denied_error) + subject.restore + end + end end describe '#empty_repo?' do diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb index b502daea418..a50329473ad 100644 --- a/spec/lib/banzai/filter/autolink_filter_spec.rb +++ b/spec/lib/banzai/filter/autolink_filter_spec.rb @@ -122,14 +122,10 @@ describe Banzai::Filter::AutolinkFilter do end it 'does not include trailing punctuation' do - doc = filter("See #{link}.") - expect(doc.at_css('a').text).to eq link - - doc = filter("See #{link}, ok?") - expect(doc.at_css('a').text).to eq link - - doc = filter("See #{link}...") - expect(doc.at_css('a').text).to eq link + ['.', ', ok?', '...', '?', '!', ': is that ok?'].each do |trailing_punctuation| + doc = filter("See #{link}#{trailing_punctuation}") + expect(doc.at_css('a').text).to eq link + end end it 'includes trailing punctuation when part of a balanced pair' do @@ -171,6 +167,15 @@ describe Banzai::Filter::AutolinkFilter do expect(actual).to eq(expected_complicated_link) end + it 'does not double-encode HTML entities' do + encoded_link = "#{link}?foo=bar&baz=quux" + expected_encoded_link = %Q{<a href="#{encoded_link}">#{encoded_link}</a>} + actual = unescape(filter(encoded_link).to_html) + + expect(actual).to eq(Rinku.auto_link(encoded_link)) + expect(actual).to eq(expected_encoded_link) + end + it 'does not include trailing HTML entities' do doc = filter("See <<<#{link}>>>") diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb new file mode 100644 index 00000000000..1fd145116df --- /dev/null +++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb @@ -0,0 +1,171 @@ +require 'spec_helper' +require 'ffaker' + +describe Banzai::Filter::CommitTrailersFilter do + include FilterSpecHelper + include CommitTrailersSpecHelper + + let(:secondary_email) { create(:email, :confirmed) } + let(:user) { create(:user) } + + let(:trailer) { "#{FFaker::Lorem.word}-by:"} + + let(:commit_message) { trailer_line(trailer, user.name, user.email) } + let(:commit_message_html) { commit_html(commit_message) } + + context 'detects' do + let(:email) { FFaker::Internet.email } + + it 'trailers in the form of *-by and replace users with links' do + doc = filter(commit_message_html) + + expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer) + end + + it 'trailers prefixed with whitespaces' do + message_html = commit_html("\n\r #{commit_message}") + + doc = filter(message_html) + + expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer) + end + + it 'GitLab users via a secondary email' do + _, message_html = build_commit_message( + trailer: trailer, + name: secondary_email.user.name, + email: secondary_email.email + ) + + doc = filter(message_html) + + expect_to_have_user_link_with_avatar( + doc, + user: secondary_email.user, + trailer: trailer, + email: secondary_email.email + ) + end + + it 'non GitLab users and replaces them with mailto links' do + _, message_html = build_commit_message( + trailer: trailer, + name: FFaker::Name.name, + email: email + ) + + doc = filter(message_html) + + expect_to_have_mailto_link(doc, email: email, trailer: trailer) + end + + it 'multiple trailers in the same message' do + different_trailer = "#{FFaker::Lorem.word}-by:" + message = commit_html %( + #{commit_message} + #{trailer_line(different_trailer, FFaker::Name.name, email)} + ) + + doc = filter(message) + + expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer) + expect_to_have_mailto_link(doc, email: email, trailer: different_trailer) + end + + context 'special names' do + where(:name) do + [ + 'John S. Doe', + 'L33t H@x0r' + ] + end + + with_them do + it do + message, message_html = build_commit_message( + trailer: trailer, + name: name, + email: email + ) + + doc = filter(message_html) + + expect_to_have_mailto_link(doc, email: email, trailer: trailer) + expect(doc.text).to match Regexp.escape(message) + end + end + end + end + + context "ignores" do + it 'commit messages without trailers' do + exp = message = commit_html(FFaker::Lorem.sentence) + doc = filter(message) + + expect(doc.to_html).to match Regexp.escape(exp) + end + + it 'trailers that are inline the commit message body' do + message = commit_html %( + #{FFaker::Lorem.sentence} #{commit_message} #{FFaker::Lorem.sentence} + ) + + doc = filter(message) + + expect(doc.css('a').size).to eq 0 + end + end + + context "structure" do + it 'preserves the commit trailer structure' do + doc = filter(commit_message_html) + + expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer) + expect(doc.text).to match Regexp.escape(commit_message) + end + + it 'preserves the original name used in the commit message' do + message, message_html = build_commit_message( + trailer: trailer, + name: FFaker::Name.name, + email: user.email + ) + + doc = filter(message_html) + + expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer) + expect(doc.text).to match Regexp.escape(message) + end + + it 'preserves the original email used in the commit message' do + message, message_html = build_commit_message( + trailer: trailer, + name: secondary_email.user.name, + email: secondary_email.email + ) + + doc = filter(message_html) + + expect_to_have_user_link_with_avatar( + doc, + user: secondary_email.user, + trailer: trailer, + email: secondary_email.email + ) + expect(doc.text).to match Regexp.escape(message) + end + + it 'only replaces trailer lines not the full commit message' do + commit_body = FFaker::Lorem.paragraph + message = commit_html %( + #{commit_body} + #{commit_message} + ) + + doc = filter(message) + + expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer) + expect(doc.text).to include(commit_body) + end + end +end diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb index 17347768a49..a5373517ac8 100644 --- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb +++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb @@ -8,6 +8,7 @@ describe Banzai::Filter::IssuableStateFilter do let(:context) { { current_user: user, issuable_state_filter_enabled: true } } let(:closed_issue) { create_issue(:closed) } let(:project) { create(:project, :public) } + let(:group) { create(:group) } let(:other_project) { create(:project, :public) } def create_link(text, data) @@ -77,6 +78,13 @@ describe Banzai::Filter::IssuableStateFilter do expect(doc.css('a').last.text).to eq("#{closed_issue.to_reference(other_project)} (closed)") end + it 'handles references from group scopes' do + link = create_link(closed_issue.to_reference(other_project), issue: closed_issue.id, reference_type: 'issue') + doc = filter(link, context.merge(project: nil, group: group)) + + expect(doc.css('a').last.text).to eq("#{closed_issue.to_reference(other_project)} (closed)") + end + it 'skips cross project references if the user cannot read cross project' do expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } link = create_link(closed_issue.to_reference(other_project), issue: closed_issue.id, reference_type: 'issue') diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index eeb82822f68..a1dd72c498f 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -196,6 +196,41 @@ describe Banzai::Filter::MergeRequestReferenceFilter do end end + context 'URL reference for a commit' do + let(:mr) { create(:merge_request, :with_diffs) } + let(:reference) do + urls.project_merge_request_url(mr.project, mr) + "/diffs?commit_id=#{mr.diff_head_sha}" + end + let(:commit) { mr.commits.find { |commit| commit.sha == mr.diff_head_sha } } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')) + .to eq reference + end + + it 'has valid text' do + doc = reference_filter("See #{reference}") + + expect(doc.text).to eq("See #{mr.to_reference(full: true)} (#{commit.short_id})") + end + + it 'has valid title attribute' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('title')).to eq(commit.title) + end + + it 'ignores invalid commit short_ids on link text' do + invalidate_commit_reference = + urls.project_merge_request_url(mr.project, mr) + "/diffs?commit_id=12345678" + doc = reference_filter("See #{invalidate_commit_reference}") + + expect(doc.text).to eq("See #{mr.to_reference(full: true)} (diffs)") + end + end + context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, :public, namespace: namespace) } diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 2a0e19ae796..e1782cff81a 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -48,7 +48,7 @@ module Gitlab }, 'images' => { input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]', - output: "<img src=\"https://localhost.com/image.png\" alt=\"Alt text\">" + output: "<div>\n<p><span><img src=\"https://localhost.com/image.png\" alt='Alt text\" onerror=\"alert(7)'></span></p>\n</div>" }, 'pre' => { input: '```mypre"><script>alert(3)</script>', diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb index 9b3916bf9e3..6b251d824f7 100644 --- a/spec/lib/gitlab/auth/ldap/access_spec.rb +++ b/spec/lib/gitlab/auth/ldap/access_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Auth::LDAP::Access do + include LdapHelpers + let(:access) { described_class.new user } let(:user) { create(:omniauth_user) } @@ -32,8 +34,10 @@ describe Gitlab::Auth::LDAP::Access do end context 'when the user is found' do + let(:ldap_user) { Gitlab::Auth::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } + before do - allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user) end context 'and the user is disabled via active directory' do @@ -120,6 +124,22 @@ describe Gitlab::Auth::LDAP::Access do end end end + + context 'when the connection fails' do + before do + raise_ldap_connection_error + end + + it 'does not block the user' do + access.allowed? + + expect(user.ldap_blocked?).to be_falsey + end + + it 'denies access' do + expect(access.allowed?).to be_falsey + end + end end describe '#block_user' do diff --git a/spec/lib/gitlab/auth/ldap/adapter_spec.rb b/spec/lib/gitlab/auth/ldap/adapter_spec.rb index 10c60d792bd..3eeaf3862f6 100644 --- a/spec/lib/gitlab/auth/ldap/adapter_spec.rb +++ b/spec/lib/gitlab/auth/ldap/adapter_spec.rb @@ -124,16 +124,36 @@ describe Gitlab::Auth::LDAP::Adapter do context "when the search raises an LDAP exception" do before do + allow(adapter).to receive(:renew_connection_adapter).and_return(ldap) allow(ldap).to receive(:search) { raise Net::LDAP::Error, "some error" } allow(Rails.logger).to receive(:warn) end - it { is_expected.to eq [] } + context 'retries the operation' do + before do + stub_const("#{described_class}::MAX_SEARCH_RETRIES", 3) + end + + it 'as many times as MAX_SEARCH_RETRIES' do + expect(ldap).to receive(:search).exactly(3).times + expect { subject }.to raise_error(Gitlab::Auth::LDAP::LDAPConnectionError) + end + + context 'when no more retries' do + before do + stub_const("#{described_class}::MAX_SEARCH_RETRIES", 1) + end - it 'logs the error' do - subject - expect(Rails.logger).to have_received(:warn).with( - "LDAP search raised exception Net::LDAP::Error: some error") + it 'raises the exception' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::LDAPConnectionError) + end + + it 'logs the error' do + expect { subject }.to raise_error(Gitlab::Auth::LDAP::LDAPConnectionError) + expect(Rails.logger).to have_received(:warn).with( + "LDAP search raised exception Net::LDAP::Error: some error") + end + end end end end diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb index 0c71f1d8ca6..64f3d09a25b 100644 --- a/spec/lib/gitlab/auth/o_auth/user_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Auth::OAuth::User do + include LdapHelpers + let(:oauth_user) { described_class.new(auth_hash) } let(:gl_user) { oauth_user.gl_user } let(:uid) { 'my-uid' } @@ -38,10 +40,6 @@ describe Gitlab::Auth::OAuth::User do end describe '#save' do - def stub_ldap_config(messages) - allow(Gitlab::Auth::LDAP::Config).to receive_messages(messages) - end - let(:provider) { 'twitter' } describe 'when account exists on server' do @@ -269,20 +267,47 @@ describe Gitlab::Auth::OAuth::User do end context 'when an LDAP person is not found by uid' do - it 'tries to find an LDAP person by DN and adds the omniauth identity to the user' do + it 'tries to find an LDAP person by email and adds the omniauth identity to the user' do allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil) - allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(ldap_user) + + oauth_user.save + + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array(result_identities(dn, uid)) + end + + context 'when also not found by email' do + it 'tries to find an LDAP person by DN and adds the omniauth identity to the user' do + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(nil) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user) + + oauth_user.save + + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array(result_identities(dn, uid)) + end + end + end + def result_identities(dn, uid) + [ + { provider: 'ldapmain', extern_uid: dn }, + { provider: 'twitter', extern_uid: uid } + ] + end + + context 'when there is an LDAP connection error' do + before do + raise_ldap_connection_error + end + + it 'does not save the identity' do oauth_user.save identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } - expect(identities_as_hash) - .to match_array( - [ - { provider: 'ldapmain', extern_uid: dn }, - { provider: 'twitter', extern_uid: uid } - ] - ) + expect(identities_as_hash).to match_array([{ provider: 'twitter', extern_uid: uid }]) end end end @@ -739,4 +764,19 @@ describe Gitlab::Auth::OAuth::User do expect(oauth_user.find_user).to eql gl_user end end + + describe '#find_ldap_person' do + context 'when LDAP connection fails' do + before do + raise_ldap_connection_error + end + + it 'returns nil' do + adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') + hash = OmniAuth::AuthHash.new(uid: 'whatever', provider: 'ldapmain') + + expect(oauth_user.send(:find_ldap_person, hash, adapter)).to be_nil + end + end + end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index f969f9e8e38..18cef8ec996 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -315,13 +315,19 @@ describe Gitlab::Auth do it "tries to autheticate with db before ldap" do expect(Gitlab::Auth::LDAP::Authentication).not_to receive(:login) - gl_auth.find_with_user_password(username, password) + expect(gl_auth.find_with_user_password(username, password)).to eq(user) + end + + it "does not find user by using ldap as fallback to for authentication" do + expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil) + + expect(gl_auth.find_with_user_password('ldap_user', 'password')).to be_nil end - it "uses ldap as fallback to for authentication" do - expect(Gitlab::Auth::LDAP::Authentication).to receive(:login) + it "find new user by using ldap as fallback to for authentication" do + expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(user) - gl_auth.find_with_user_password('ldap_user', 'password') + expect(gl_auth.find_with_user_password('ldap_user', 'password')).to eq(user) end end diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb index e112e9e9e3d..5ce84c61042 100644 --- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb @@ -51,4 +51,20 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201 expect { described_class.new.perform(1, 6) } .to raise_error ActiveRecord::RecordNotUnique end + + context 'when invalid class can be loaded due to single table inheritance' do + let(:commit_status) do + jobs.create!(id: 7, commit_id: 1, project_id: 123, stage_idx: 4, + stage: 'post-deploy', status: :failed) + end + + before do + commit_status.update_column(:type, 'SomeClass') + end + + it 'does ignore single table inheritance type' do + expect { described_class.new.perform(1, 7) }.not_to raise_error + expect(jobs.find(7)).to have_attributes(stage_id: (a_value > 0)) + end + end end diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index 5cb1f4deb5f..0dc3705825d 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -54,7 +54,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do context 'hashed storage' do let(:gitlab_shell) { Gitlab::Shell.new } let(:repository_storage) { 'default' } - let(:root_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:root_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } let(:hashed_path) { "@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" } let(:repo_path) { File.join(root_path, "#{hashed_path}.git") } diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index a6a1d9e619f..c63120b0b29 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -137,7 +137,7 @@ describe Gitlab::BitbucketImport::Importer do it 'imports to the project disk_path' do expect(project.wiki).to receive(:repository_exists?) { false } expect(importer.gitlab_shell).to receive(:import_repository).with( - project.repository_storage_path, + project.repository_storage, project.wiki.disk_path, project.import_url + '/wiki' ) diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb index e263d29656c..8e9386b1ba1 100644 --- a/spec/lib/gitlab/checks/project_moved_spec.rb +++ b/spec/lib/gitlab/checks/project_moved_spec.rb @@ -44,44 +44,17 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do end describe '#message' do - context 'when the push is rejected' do - it 'returns a redirect message telling the user to try again' do - project_moved = described_class.new(project, user, 'http', 'foo/bar') - message = "Project 'foo/bar' was moved to '#{project.full_path}'." + - "\n\nPlease update your Git remote:" + - "\n\n git remote set-url origin #{project.http_url_to_repo} and try again.\n" + it 'returns a redirect message' do + project_moved = described_class.new(project, user, 'http', 'foo/bar') + message = <<~MSG + Project 'foo/bar' was moved to '#{project.full_path}'. - expect(project_moved.message(rejected: true)).to eq(message) - end - end + Please update your Git remote: - context 'when the push is not rejected' do - it 'returns a redirect message' do - project_moved = described_class.new(project, user, 'http', 'foo/bar') - message = "Project 'foo/bar' was moved to '#{project.full_path}'." + - "\n\nPlease update your Git remote:" + - "\n\n git remote set-url origin #{project.http_url_to_repo}\n" + git remote set-url origin #{project.http_url_to_repo} + MSG - expect(project_moved.message).to eq(message) - end - end - end - - describe '#permanent_redirect?' do - context 'with a permanent RedirectRoute' do - it 'returns true' do - project.route.create_redirect('foo/bar', permanent: true) - project_moved = described_class.new(project, user, 'http', 'foo/bar') - expect(project_moved.permanent_redirect?).to be_truthy - end - end - - context 'without a permanent RedirectRoute' do - it 'returns false' do - project.route.create_redirect('foo/bar') - project_moved = described_class.new(project, user, 'http', 'foo/bar') - expect(project_moved.permanent_redirect?).to be_falsy - end + expect(project_moved.message).to eq(message) end end end diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb new file mode 100644 index 00000000000..2ce858836e3 --- /dev/null +++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe Gitlab::Ci::Build::Policy::Variables do + set(:project) { create(:project) } + + let(:pipeline) do + build(:ci_empty_pipeline, project: project, ref: 'master', source: :push) + end + + let(:ci_build) do + build(:ci_build, pipeline: pipeline, project: project, ref: 'master') + end + + let(:seed) { double('build seed', to_resource: ci_build) } + + before do + pipeline.variables.build(key: 'CI_PROJECT_NAME', value: '') + end + + describe '#satisfied_by?' do + it 'is satisfied by at least one matching statement' do + policy = described_class.new(['$CI_PROJECT_ID', '$UNDEFINED']) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied by an overriden empty variable' do + policy = described_class.new(['$CI_PROJECT_NAME']) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + + it 'is satisfied by a truthy pipeline expression' do + policy = described_class.new([%($CI_PIPELINE_SOURCE == "push")]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied by a falsy pipeline expression' do + policy = described_class.new([%($CI_PIPELINE_SOURCE == "invalid source")]) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + + it 'is satisfied by a truthy expression using undefined variable' do + policy = described_class.new(['$UNDEFINED == null']) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied by a falsy expression using undefined variable' do + policy = described_class.new(['$UNDEFINED']) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + + it 'allows to evaluate regular secret variables' do + create(:ci_variable, project: project, key: 'SECRET', value: 'my secret') + + policy = described_class.new(["$SECRET == 'my secret'"]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'does not persist neither pipeline nor build' do + described_class.new('$VAR').satisfied_by?(pipeline, seed) + + expect(pipeline).not_to be_persisted + expect(seed.to_resource).not_to be_persisted + end + end +end diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb index 5a21282712a..cce4efaa069 100644 --- a/spec/lib/gitlab/ci/build/step_spec.rb +++ b/spec/lib/gitlab/ci/build/step_spec.rb @@ -5,10 +5,14 @@ describe Gitlab::Ci::Build::Step do shared_examples 'has correct script' do subject { described_class.from_commands(job) } + before do + job.run! + end + it 'fabricates an object' do expect(subject.name).to eq(:script) expect(subject.script).to eq(script) - expect(subject.timeout).to eq(job.timeout) + expect(subject.timeout).to eq(job.metadata_timeout) expect(subject.when).to eq('on_success') expect(subject.allow_failure).to be_falsey end @@ -47,6 +51,10 @@ describe Gitlab::Ci::Build::Step do subject { described_class.from_after_script(job) } + before do + job.run! + end + context 'when after_script is empty' do it 'doesn not fabricate an object' do is_expected.to be_nil @@ -59,7 +67,7 @@ describe Gitlab::Ci::Build::Step do it 'fabricates an object' do expect(subject.name).to eq(:after_script) expect(subject.script).to eq(['ls -la', 'date']) - expect(subject.timeout).to eq(job.timeout) + expect(subject.timeout).to eq(job.metadata_timeout) expect(subject.when).to eq('always') expect(subject.allow_failure).to be_truthy end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 5e83abf645b..08718c382b9 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -83,6 +83,39 @@ describe Gitlab::Ci::Config::Entry::Policy do end end + context 'when specifying valid variables expressions policy' do + let(:config) { { variables: ['$VAR == null'] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when specifying variables expressions in invalid format' do + let(:config) { { variables: '$MY_VAR' } } + + it 'reports an error about invalid format' do + expect(entry.errors).to include /should be an array of strings/ + end + end + + context 'when specifying invalid variables expressions statement' do + let(:config) { { variables: ['$MY_VAR =='] } } + + it 'reports an error about invalid statement' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when specifying invalid variables expressions token' do + let(:config) { { variables: ['$MY_VAR == 123'] } } + + it 'reports an error about invalid statement' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + context 'when specifying unknown policy' do let(:config) { { refs: ['master'], invalid: :something } } diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb index 1b03227d67b..dc12ba076bc 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -5,23 +5,23 @@ describe Gitlab::Ci::Pipeline::Chain::Create do set(:user) { create(:user) } let(:pipeline) do - build(:ci_pipeline_with_one_job, project: project, - ref: 'master') + build(:ci_empty_pipeline, project: project, ref: 'master') end let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( - project: project, - current_user: user, seeds_block: nil) + project: project, current_user: user) end let(:step) { described_class.new(pipeline, command) } - before do - step.perform! - end - context 'when pipeline is ready to be saved' do + before do + pipeline.stages.build(name: 'test', project: project) + + step.perform! + end + it 'saves a pipeline' do expect(pipeline).to be_persisted end @@ -32,6 +32,7 @@ describe Gitlab::Ci::Pipeline::Chain::Create do it 'creates stages' do expect(pipeline.reload.stages).to be_one + expect(pipeline.stages.first).to be_persisted end end @@ -40,6 +41,10 @@ describe Gitlab::Ci::Pipeline::Chain::Create do build(:ci_pipeline, project: project, ref: nil) end + before do + step.perform! + end + it 'breaks the chain' do expect(step.break?).to be true end @@ -49,18 +54,4 @@ describe Gitlab::Ci::Pipeline::Chain::Create do .to include /Failed to persist the pipeline/ end end - - context 'when there is a seed block present' do - let(:seeds) { spy('pipeline seeds') } - - let(:command) do - double('command', project: project, - current_user: user, - seeds_block: seeds) - end - - it 'executes the block' do - expect(seeds).to have_received(:call).with(pipeline) - end - end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb new file mode 100644 index 00000000000..8312fa47cfa --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -0,0 +1,158 @@ +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Populate do + set(:project) { create(:project) } + set(:user) { create(:user) } + + let(:pipeline) do + build(:ci_pipeline_with_one_job, project: project, + ref: 'master', + user: user) + end + + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + project: project, + current_user: user, + seeds_block: nil) + end + + let(:step) { described_class.new(pipeline, command) } + + context 'when pipeline doesn not have seeds block' do + before do + step.perform! + end + + it 'does not persist the pipeline' do + expect(pipeline).not_to be_persisted + end + + it 'does not break the chain' do + expect(step.break?).to be false + end + + it 'populates pipeline with stages' do + expect(pipeline.stages).to be_one + expect(pipeline.stages.first).not_to be_persisted + end + + it 'populates pipeline with builds' do + expect(pipeline.builds).to be_one + expect(pipeline.builds.first).not_to be_persisted + expect(pipeline.stages.first.builds).to be_one + expect(pipeline.stages.first.builds.first).not_to be_persisted + end + + it 'correctly assigns user' do + expect(pipeline.builds).to all(have_attributes(user: user)) + end + end + + context 'when pipeline is empty' do + let(:config) do + { rspec: { + script: 'ls', + only: ['something'] + } } + end + + let(:pipeline) do + build(:ci_pipeline, project: project, config: config) + end + + before do + step.perform! + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'appends an error about missing stages' do + expect(pipeline.errors.to_a) + .to include 'No stages / jobs for this pipeline.' + end + end + + context 'when pipeline has validation errors' do + let(:pipeline) do + build(:ci_pipeline, project: project, ref: nil) + end + + before do + step.perform! + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'appends validation error' do + expect(pipeline.errors.to_a) + .to include 'Failed to build the pipeline!' + end + end + + context 'when there is a seed blocks present' do + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + project: project, + current_user: user, + seeds_block: seeds_block) + end + + context 'when seeds block builds some resources' do + let(:seeds_block) do + ->(pipeline) { pipeline.variables.build(key: 'VAR', value: '123') } + end + + it 'populates pipeline with resources described in the seeds block' do + step.perform! + + expect(pipeline).not_to be_persisted + expect(pipeline.variables).not_to be_empty + expect(pipeline.variables.first).not_to be_persisted + expect(pipeline.variables.first.key).to eq 'VAR' + expect(pipeline.variables.first.value).to eq '123' + end + end + + context 'when seeds block tries to persist some resources' do + let(:seeds_block) do + ->(pipeline) { pipeline.variables.create!(key: 'VAR', value: '123') } + end + + it 'raises exception' do + expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved) + end + end + end + + context 'when pipeline gets persisted during the process' do + let(:pipeline) { create(:ci_pipeline, project: project) } + + it 'raises error' do + expect { step.perform! }.to raise_error(described_class::PopulateError) + end + end + + context 'when using only/except build policies' do + let(:config) do + { rspec: { script: 'rspec', stage: 'test', only: ['master'] }, + prod: { script: 'cap prod', stage: 'deploy', only: ['tags'] } } + end + + let(:pipeline) do + build(:ci_pipeline, ref: 'master', config: config) + end + + it 'populates pipeline according to used policies' do + step.perform! + + expect(pipeline.stages.size).to eq 1 + expect(pipeline.builds.size).to eq 1 + expect(pipeline.builds.first.name).to eq 'rspec' + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb index 5c12c6e6392..c53294d091c 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb @@ -76,28 +76,6 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do end end - context 'when pipeline has no stages / jobs' do - let(:config) do - { rspec: { - script: 'ls', - only: ['something'] - } } - end - - let(:pipeline) do - build(:ci_pipeline, project: project, config: config) - end - - it 'appends an error about missing stages' do - expect(pipeline.errors.to_a) - .to include 'No stages / jobs for this pipeline.' - end - - it 'breaks the chain' do - expect(step.break?).to be true - end - end - context 'when pipeline contains configuration validation errors' do let(:config) { { rspec: {} } } diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb index 86234dfb9e5..1ccb792d1da 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb @@ -73,6 +73,22 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do expect(token).not_to be_nil expect(token.build.evaluate).to eq 'some " string' end + + it 'allows to use an empty string inside single quotes' do + scanner = StringScanner.new(%('')) + + token = described_class.scan(scanner) + + expect(token.build.evaluate).to eq '' + end + + it 'allow to use an empty string inside double quotes' do + scanner = StringScanner.new(%("")) + + token = described_class.scan(scanner) + + expect(token.build.evaluate).to eq '' + end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index 472a58599d8..6685bf5385b 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -1,14 +1,23 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Expression::Statement do - let(:pipeline) { build(:ci_pipeline) } - subject do - described_class.new(text, pipeline) + described_class.new(text, variables) + end + + let(:variables) do + { 'PRESENT_VARIABLE' => 'my variable', + EMPTY_VARIABLE: '' } end - before do - pipeline.variables.build([key: 'VARIABLE', value: 'my variable']) + describe '.new' do + context 'when variables are not provided' do + it 'allows to properly initializes the statement' do + statement = described_class.new('$PRESENT_VARIABLE') + + expect(statement.evaluate).to be_nil + end + end end describe '#parse_tree' do @@ -23,18 +32,26 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do context 'when expression grammar is incorrect' do table = [ - '$VAR "text"', # missing operator - '== "123"', # invalid right side - "'single quotes'", # single quotes string - '$VAR ==', # invalid right side - '12345', # unknown syntax - '' # empty statement + '$VAR "text"', # missing operator + '== "123"', # invalid left side + '"some string"', # only string provided + '$VAR ==', # invalid right side + '12345', # unknown syntax + '' # empty statement ] table.each do |syntax| - it "raises an error when syntax is `#{syntax}`" do - expect { described_class.new(syntax, pipeline).parse_tree } - .to raise_error described_class::StatementError + context "when expression grammar is #{syntax.inspect}" do + let(:text) { syntax } + + it 'aises a statement error exception' do + expect { subject.parse_tree } + .to raise_error described_class::StatementError + end + + it 'is an invalid statement' do + expect(subject).not_to be_valid + end end end end @@ -47,10 +64,14 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do expect(subject.parse_tree) .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals end + + it 'is a valid statement' do + expect(subject).to be_valid + end end context 'when using a single token' do - let(:text) { '$VARIABLE' } + let(:text) { '$PRESENT_VARIABLE' } it 'returns a single token instance' do expect(subject.parse_tree) @@ -62,14 +83,17 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do describe '#evaluate' do statements = [ - ['$VARIABLE == "my variable"', true], - ["$VARIABLE == 'my variable'", true], - ['"my variable" == $VARIABLE', true], - ['$VARIABLE == null', false], - ['$VAR == null', true], - ['null == $VAR', true], - ['$VARIABLE', 'my variable'], - ['$VAR', nil] + ['$PRESENT_VARIABLE == "my variable"', true], + ["$PRESENT_VARIABLE == 'my variable'", true], + ['"my variable" == $PRESENT_VARIABLE', true], + ['$PRESENT_VARIABLE == null', false], + ['$EMPTY_VARIABLE == null', false], + ['"" == $EMPTY_VARIABLE', true], + ['$EMPTY_VARIABLE', ''], + ['$UNDEFINED_VARIABLE == null', true], + ['null == $UNDEFINED_VARIABLE', true], + ['$PRESENT_VARIABLE', 'my variable'], + ['$UNDEFINED_VARIABLE', nil] ] statements.each do |expression, value| @@ -82,4 +106,25 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do end end end + + describe '#truthful?' do + statements = [ + ['$PRESENT_VARIABLE == "my variable"', true], + ["$PRESENT_VARIABLE == 'no match'", false], + ['$UNDEFINED_VARIABLE == null', true], + ['$PRESENT_VARIABLE', true], + ['$UNDEFINED_VARIABLE', false], + ['$EMPTY_VARIABLE', false] + ] + + statements.each do |expression, value| + context "when using expression `#{expression}`" do + let(:text) { expression } + + it "returns `#{value.inspect}`" do + expect(subject.truthful?).to eq value + end + end + end + end end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb new file mode 100644 index 00000000000..fffa727c2ed --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -0,0 +1,232 @@ +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Seed::Build do + let(:pipeline) { create(:ci_empty_pipeline) } + + let(:attributes) do + { name: 'rspec', + ref: 'master', + commands: 'rspec' } + end + + subject do + described_class.new(pipeline, attributes) + end + + describe '#attributes' do + it 'returns hash attributes of a build' do + expect(subject.attributes).to be_a Hash + expect(subject.attributes) + .to include(:name, :project, :ref, :commands) + end + end + + describe '#to_resource' do + it 'returns a valid build resource' do + expect(subject.to_resource).to be_a(::Ci::Build) + expect(subject.to_resource).to be_valid + end + + it 'memoizes a resource object' do + build = subject.to_resource + + expect(build.object_id).to eq subject.to_resource.object_id + end + + it 'can not be persisted without explicit assignment' do + build = subject.to_resource + + pipeline.save! + + expect(build).not_to be_persisted + end + end + + describe 'applying only/except policies' do + context 'when no branch policy is specified' do + let(:attributes) { { name: 'rspec' } } + + it { is_expected.to be_included } + end + + context 'when branch policy does not match' do + context 'when using only' do + let(:attributes) { { name: 'rspec', only: { refs: ['deploy'] } } } + + it { is_expected.not_to be_included } + end + + context 'when using except' do + let(:attributes) { { name: 'rspec', except: { refs: ['deploy'] } } } + + it { is_expected.to be_included } + end + end + + context 'when branch regexp policy does not match' do + context 'when using only' do + let(:attributes) { { name: 'rspec', only: { refs: ['/^deploy$/'] } } } + + it { is_expected.not_to be_included } + end + + context 'when using except' do + let(:attributes) { { name: 'rspec', except: { refs: ['/^deploy$/'] } } } + + it { is_expected.to be_included } + end + end + + context 'when branch policy matches' do + context 'when using only' do + let(:attributes) { { name: 'rspec', only: { refs: %w[deploy master] } } } + + it { is_expected.to be_included } + end + + context 'when using except' do + let(:attributes) { { name: 'rspec', except: { refs: %w[deploy master] } } } + + it { is_expected.not_to be_included } + end + end + + context 'when keyword policy matches' do + context 'when using only' do + let(:attributes) { { name: 'rspec', only: { refs: ['branches'] } } } + + it { is_expected.to be_included } + end + + context 'when using except' do + let(:attributes) { { name: 'rspec', except: { refs: ['branches'] } } } + + it { is_expected.not_to be_included } + end + end + + context 'when keyword policy does not match' do + context 'when using only' do + let(:attributes) { { name: 'rspec', only: { refs: ['tags'] } } } + + it { is_expected.not_to be_included } + end + + context 'when using except' do + let(:attributes) { { name: 'rspec', except: { refs: ['tags'] } } } + + it { is_expected.to be_included } + end + end + + context 'when keywords and pipeline source policy matches' do + possibilities = [%w[pushes push], + %w[web web], + %w[triggers trigger], + %w[schedules schedule], + %w[api api], + %w[external external]] + + context 'when using only' do + possibilities.each do |keyword, source| + context "when using keyword `#{keyword}` and source `#{source}`" do + let(:pipeline) do + build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) + end + + let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } } + + it { is_expected.to be_included } + end + end + end + + context 'when using except' do + possibilities.each do |keyword, source| + context "when using keyword `#{keyword}` and source `#{source}`" do + let(:pipeline) do + build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) + end + + let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } } + + it { is_expected.not_to be_included } + end + end + end + end + + context 'when keywords and pipeline source does not match' do + possibilities = [%w[pushes web], + %w[web push], + %w[triggers schedule], + %w[schedules external], + %w[api trigger], + %w[external api]] + + context 'when using only' do + possibilities.each do |keyword, source| + context "when using keyword `#{keyword}` and source `#{source}`" do + let(:pipeline) do + build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) + end + + let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } } + + it { is_expected.not_to be_included } + end + end + end + + context 'when using except' do + possibilities.each do |keyword, source| + context "when using keyword `#{keyword}` and source `#{source}`" do + let(:pipeline) do + build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) + end + + let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } } + + it { is_expected.to be_included } + end + end + end + end + + context 'when repository path matches' do + context 'when using only' do + let(:attributes) do + { name: 'rspec', only: { refs: ["branches@#{pipeline.project_full_path}"] } } + end + + it { is_expected.to be_included } + end + + context 'when using except' do + let(:attributes) do + { name: 'rspec', except: { refs: ["branches@#{pipeline.project_full_path}"] } } + end + + it { is_expected.not_to be_included } + end + end + + context 'when repository path does not matches' do + context 'when using only' do + let(:attributes) do + { name: 'rspec', only: { refs: ['branches@fork'] } } + end + + it { is_expected.not_to be_included } + end + + context 'when using except' do + let(:attributes) do + { name: 'rspec', except: { refs: ['branches@fork'] } } + end + + it { is_expected.to be_included } + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb new file mode 100644 index 00000000000..eb1b285c7bd --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Seed::Stage do + let(:pipeline) { create(:ci_empty_pipeline) } + + let(:attributes) do + { name: 'test', + index: 0, + builds: [{ name: 'rspec' }, + { name: 'spinach' }, + { name: 'deploy', only: { refs: ['feature'] } }] } + end + + subject do + described_class.new(pipeline, attributes) + end + + describe '#size' do + it 'returns a number of jobs in the stage' do + expect(subject.size).to eq 2 + end + end + + describe '#attributes' do + it 'returns hash attributes of a stage' do + expect(subject.attributes).to be_a Hash + expect(subject.attributes).to include(:name, :project) + end + end + + describe '#included?' do + context 'when it contains builds seeds' do + let(:attributes) do + { name: 'test', + index: 0, + builds: [{ name: 'deploy', only: { refs: ['master'] } }] } + end + + it { is_expected.to be_included } + end + + context 'when it does not contain build seeds' do + let(:attributes) do + { name: 'test', + index: 0, + builds: [{ name: 'deploy', only: { refs: ['feature'] } }] } + end + + it { is_expected.not_to be_included } + end + end + + describe '#seeds' do + it 'returns build seeds' do + expect(subject.seeds).to all(be_a Gitlab::Ci::Pipeline::Seed::Build) + end + + it 'returns build seeds including valid attributes' do + expect(subject.seeds.size).to eq 2 + expect(subject.seeds.map(&:attributes)).to all(include(ref: 'master')) + expect(subject.seeds.map(&:attributes)).to all(include(tag: false)) + expect(subject.seeds.map(&:attributes)).to all(include(project: pipeline.project)) + expect(subject.seeds.map(&:attributes)) + .to all(include(trigger_request: pipeline.trigger_requests.first)) + end + + context 'when a ref is protected' do + before do + allow_any_instance_of(Project).to receive(:protected_for?).and_return(true) + end + + it 'returns protected builds' do + expect(subject.seeds.map(&:attributes)).to all(include(protected: true)) + end + end + + context 'when a ref is not protected' do + before do + allow_any_instance_of(Project).to receive(:protected_for?).and_return(false) + end + + it 'returns unprotected builds' do + expect(subject.seeds.map(&:attributes)).to all(include(protected: false)) + end + end + + it 'filters seeds using only/except policies' do + expect(subject.seeds.map(&:attributes)).to satisfy do |seeds| + seeds.any? { |hash| hash.fetch(:name) == 'rspec' } + end + + expect(subject.seeds.map(&:attributes)).not_to satisfy do |seeds| + seeds.any? { |hash| hash.fetch(:name) == 'deploy' } + end + end + end + + describe '#to_resource' do + it 'builds a valid stage object with all builds' do + subject.to_resource.save! + + expect(pipeline.reload.stages.count).to eq 1 + expect(pipeline.reload.builds.count).to eq 2 + expect(pipeline.builds).to all(satisfy { |job| job.stage_id.present? }) + expect(pipeline.builds).to all(satisfy { |job| job.pipeline.present? }) + expect(pipeline.builds).to all(satisfy { |job| job.project.present? }) + expect(pipeline.stages) + .to all(satisfy { |stage| stage.pipeline.present? }) + expect(pipeline.stages) + .to all(satisfy { |stage| stage.project.present? }) + end + + it 'can not be persisted without explicit pipeline assignment' do + stage = subject.to_resource + + pipeline.save! + + expect(stage).not_to be_persisted + expect(pipeline.reload.stages.count).to eq 0 + expect(pipeline.reload.builds.count).to eq 0 + end + end +end diff --git a/spec/lib/gitlab/ci/stage/seed_spec.rb b/spec/lib/gitlab/ci/stage/seed_spec.rb deleted file mode 100644 index 3fe8d50c49a..00000000000 --- a/spec/lib/gitlab/ci/stage/seed_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Stage::Seed do - let(:pipeline) { create(:ci_empty_pipeline) } - - let(:builds) do - [{ name: 'rspec' }, { name: 'spinach' }] - end - - subject do - described_class.new(pipeline, 'test', builds) - end - - describe '#size' do - it 'returns a number of jobs in the stage' do - expect(subject.size).to eq 2 - end - end - - describe '#stage' do - it 'returns hash attributes of a stage' do - expect(subject.stage).to be_a Hash - expect(subject.stage).to include(:name, :project) - end - end - - describe '#builds' do - it 'returns hash attributes of all builds' do - expect(subject.builds.size).to eq 2 - expect(subject.builds).to all(include(ref: 'master')) - expect(subject.builds).to all(include(tag: false)) - expect(subject.builds).to all(include(project: pipeline.project)) - expect(subject.builds) - .to all(include(trigger_request: pipeline.trigger_requests.first)) - end - - context 'when a ref is protected' do - before do - allow_any_instance_of(Project).to receive(:protected_for?).and_return(true) - end - - it 'returns protected builds' do - expect(subject.builds).to all(include(protected: true)) - end - end - - context 'when a ref is unprotected' do - before do - allow_any_instance_of(Project).to receive(:protected_for?).and_return(false) - end - - it 'returns unprotected builds' do - expect(subject.builds).to all(include(protected: false)) - end - end - end - - describe '#user=' do - let(:user) { build(:user) } - - it 'assignes relevant pipeline attributes' do - subject.user = user - - expect(subject.builds).to all(include(user: user)) - end - end - - describe '#create!' do - it 'creates all stages and builds' do - subject.create! - - expect(pipeline.reload.stages.count).to eq 1 - expect(pipeline.reload.builds.count).to eq 2 - expect(pipeline.builds).to all(satisfy { |job| job.stage_id.present? }) - expect(pipeline.builds).to all(satisfy { |job| job.pipeline.present? }) - expect(pipeline.builds).to all(satisfy { |job| job.project.present? }) - expect(pipeline.stages) - .to all(satisfy { |stage| stage.pipeline.present? }) - expect(pipeline.stages) - .to all(satisfy { |stage| stage.project.present? }) - end - end -end diff --git a/spec/lib/gitlab/ci/trace/http_io_spec.rb b/spec/lib/gitlab/ci/trace/http_io_spec.rb new file mode 100644 index 00000000000..5474e2f518c --- /dev/null +++ b/spec/lib/gitlab/ci/trace/http_io_spec.rb @@ -0,0 +1,315 @@ +require 'spec_helper' + +describe Gitlab::Ci::Trace::HttpIO do + include HttpIOHelpers + + let(:http_io) { described_class.new(url, size) } + let(:url) { remote_trace_url } + let(:size) { remote_trace_size } + + describe '#close' do + subject { http_io.close } + + it { is_expected.to be_nil } + end + + describe '#binmode' do + subject { http_io.binmode } + + it { is_expected.to be_nil } + end + + describe '#binmode?' do + subject { http_io.binmode? } + + it { is_expected.to be_truthy } + end + + describe '#path' do + subject { http_io.path } + + it { is_expected.to be_nil } + end + + describe '#url' do + subject { http_io.url } + + it { is_expected.to eq(url) } + end + + describe '#seek' do + subject { http_io.seek(pos, where) } + + context 'when moves pos to end of the file' do + let(:pos) { 0 } + let(:where) { IO::SEEK_END } + + it { is_expected.to eq(size) } + end + + context 'when moves pos to middle of the file' do + let(:pos) { size / 2 } + let(:where) { IO::SEEK_SET } + + it { is_expected.to eq(size / 2) } + end + + context 'when moves pos around' do + it 'matches the result' do + expect(http_io.seek(0)).to eq(0) + expect(http_io.seek(100, IO::SEEK_CUR)).to eq(100) + expect { http_io.seek(size + 1, IO::SEEK_CUR) }.to raise_error('new position is outside of file') + end + end + end + + describe '#eof?' do + subject { http_io.eof? } + + context 'when current pos is at end of the file' do + before do + http_io.seek(size, IO::SEEK_SET) + end + + it { is_expected.to be_truthy } + end + + context 'when current pos is not at end of the file' do + before do + http_io.seek(0, IO::SEEK_SET) + end + + it { is_expected.to be_falsey } + end + end + + describe '#each_line' do + subject { http_io.each_line } + + let(:string_io) { StringIO.new(remote_trace_body) } + + before do + stub_remote_trace_206 + end + + it 'yields lines' do + expect { |b| http_io.each_line(&b) }.to yield_successive_args(*string_io.each_line.to_a) + end + + context 'when buckets on GCS' do + context 'when BUFFER_SIZE is larger than file size' do + before do + stub_remote_trace_200 + set_larger_buffer_size_than(size) + end + + it 'calls get_chunk only once' do + expect_any_instance_of(Net::HTTP).to receive(:request).once.and_call_original + + http_io.each_line { |line| } + end + end + end + end + + describe '#read' do + subject { http_io.read(length) } + + context 'when there are no network issue' do + before do + stub_remote_trace_206 + end + + context 'when read whole size' do + let(:length) { nil } + + context 'when BUFFER_SIZE is smaller than file size' do + before do + set_smaller_buffer_size_than(size) + end + + it 'reads a trace' do + is_expected.to eq(remote_trace_body) + end + end + + context 'when BUFFER_SIZE is larger than file size' do + before do + set_larger_buffer_size_than(size) + end + + it 'reads a trace' do + is_expected.to eq(remote_trace_body) + end + end + end + + context 'when read only first 100 bytes' do + let(:length) { 100 } + + context 'when BUFFER_SIZE is smaller than file size' do + before do + set_smaller_buffer_size_than(size) + end + + it 'reads a trace' do + is_expected.to eq(remote_trace_body[0, length]) + end + end + + context 'when BUFFER_SIZE is larger than file size' do + before do + set_larger_buffer_size_than(size) + end + + it 'reads a trace' do + is_expected.to eq(remote_trace_body[0, length]) + end + end + end + + context 'when tries to read oversize' do + let(:length) { size + 1000 } + + context 'when BUFFER_SIZE is smaller than file size' do + before do + set_smaller_buffer_size_than(size) + end + + it 'reads a trace' do + is_expected.to eq(remote_trace_body) + end + end + + context 'when BUFFER_SIZE is larger than file size' do + before do + set_larger_buffer_size_than(size) + end + + it 'reads a trace' do + is_expected.to eq(remote_trace_body) + end + end + end + + context 'when tries to read 0 bytes' do + let(:length) { 0 } + + context 'when BUFFER_SIZE is smaller than file size' do + before do + set_smaller_buffer_size_than(size) + end + + it 'reads a trace' do + is_expected.to be_empty + end + end + + context 'when BUFFER_SIZE is larger than file size' do + before do + set_larger_buffer_size_than(size) + end + + it 'reads a trace' do + is_expected.to be_empty + end + end + end + end + + context 'when there is anetwork issue' do + let(:length) { nil } + + before do + stub_remote_trace_500 + end + + it 'reads a trace' do + expect { subject }.to raise_error(Gitlab::Ci::Trace::HttpIO::FailedToGetChunkError) + end + end + end + + describe '#readline' do + subject { http_io.readline } + + let(:string_io) { StringIO.new(remote_trace_body) } + + before do + stub_remote_trace_206 + end + + shared_examples 'all line matching' do + it 'reads a line' do + (0...remote_trace_body.lines.count).each do + expect(http_io.readline).to eq(string_io.readline) + end + end + end + + context 'when there is anetwork issue' do + let(:length) { nil } + + before do + stub_remote_trace_500 + end + + it 'reads a trace' do + expect { subject }.to raise_error(Gitlab::Ci::Trace::HttpIO::FailedToGetChunkError) + end + end + + context 'when BUFFER_SIZE is smaller than file size' do + before do + set_smaller_buffer_size_than(size) + end + + it_behaves_like 'all line matching' + end + + context 'when BUFFER_SIZE is larger than file size' do + before do + set_larger_buffer_size_than(size) + end + + it_behaves_like 'all line matching' + end + + context 'when pos is at middle of the file' do + before do + set_smaller_buffer_size_than(size) + + http_io.seek(size / 2) + string_io.seek(size / 2) + end + + it 'reads from pos' do + expect(http_io.readline).to eq(string_io.readline) + end + end + end + + describe '#write' do + subject { http_io.write(nil) } + + it { expect { subject }.to raise_error(NotImplementedError) } + end + + describe '#truncate' do + subject { http_io.truncate(nil) } + + it { expect { subject }.to raise_error(NotImplementedError) } + end + + describe '#flush' do + subject { http_io.flush } + + it { expect { subject }.to raise_error(NotImplementedError) } + end + + describe '#present?' do + subject { http_io.present? } + + it { is_expected.to be_truthy } + end +end diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index cc1257484d2..bf9208f1ff4 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -46,9 +46,13 @@ describe Gitlab::Ci::Variables::Collection::Item do end end - describe '#to_hash' do - it 'returns a hash representation of a collection item' do - expect(described_class.new(**variable).to_hash).to eq variable + describe '#to_runner_variable' do + it 'returns a runner-compatible hash representation' do + runner_variable = described_class + .new(**variable) + .to_runner_variable + + expect(runner_variable).to eq variable end end end diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index 90b6e178242..cb2f7718c9c 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::Ci::Variables::Collection do collection = described_class.new([variable]) - expect(collection.first.to_hash).to eq variable + expect(collection.first.to_runner_variable).to eq variable end it 'can be initialized without an argument' do @@ -96,4 +96,19 @@ describe Gitlab::Ci::Variables::Collection do .to eq [{ key: 'TEST', value: 1, public: true }] end end + + describe '#to_hash' do + it 'returns regular hash in valid order without duplicates' do + collection = described_class.new + .append(key: 'TEST1', value: 'test-1') + .append(key: 'TEST2', value: 'test-2') + .append(key: 'TEST1', value: 'test-3') + + expect(collection.to_hash).to eq('TEST1' => 'test-3', + 'TEST2' => 'test-2') + + expect(collection.to_hash).to include(TEST1: 'test-3') + expect(collection.to_hash).not_to include(TEST1: 'test-1') + end + end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index f83f932e61e..ecb16daec96 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -18,6 +18,34 @@ module Gitlab describe '#build_attributes' do subject { described_class.new(config).build_attributes(:rspec) } + describe 'attributes list' do + let(:config) do + YAML.dump( + before_script: ['pwd'], + rspec: { script: 'rspec' } + ) + end + + it 'returns valid build attributes' do + expect(subject).to eq({ + stage: "test", + stage_idx: 1, + name: "rspec", + commands: "pwd\nrspec", + coverage_regex: nil, + tag_list: [], + options: { + before_script: ["pwd"], + script: ["rspec"] + }, + allow_failure: false, + when: "on_success", + environment: nil, + yaml_variables: [] + }) + end + end + describe 'coverage entry' do describe 'code coverage regexp' do let(:config) do @@ -105,512 +133,118 @@ module Gitlab end end - describe '#stage_seeds' do - context 'when no refs policy is specified' do - let(:config) do - YAML.dump(production: { stage: 'deploy', script: 'cap prod' }, - rspec: { stage: 'test', script: 'rspec' }, - spinach: { stage: 'test', script: 'spinach' }) - end - - let(:pipeline) { create(:ci_empty_pipeline) } - - it 'correctly fabricates a stage seeds object' do - seeds = subject.stage_seeds(pipeline) - - expect(seeds.size).to eq 2 - expect(seeds.first.stage[:name]).to eq 'test' - expect(seeds.second.stage[:name]).to eq 'deploy' - expect(seeds.first.builds.dig(0, :name)).to eq 'rspec' - expect(seeds.first.builds.dig(1, :name)).to eq 'spinach' - expect(seeds.second.builds.dig(0, :name)).to eq 'production' - end - end - - context 'when refs policy is specified' do - let(:config) do - YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['master'] }, - spinach: { stage: 'test', script: 'spinach', only: ['tags'] }) - end - - let(:pipeline) do - create(:ci_empty_pipeline, ref: 'feature', tag: true) - end - - it 'returns stage seeds only assigned to master to master' do - seeds = subject.stage_seeds(pipeline) - - expect(seeds.size).to eq 1 - expect(seeds.first.stage[:name]).to eq 'test' - expect(seeds.first.builds.dig(0, :name)).to eq 'spinach' - end - end - - context 'when source policy is specified' do - let(:config) do - YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] }, - spinach: { stage: 'test', script: 'spinach', only: ['schedules'] }) - end - - let(:pipeline) do - create(:ci_empty_pipeline, source: :schedule) - end - - it 'returns stage seeds only assigned to schedules' do - seeds = subject.stage_seeds(pipeline) - - expect(seeds.size).to eq 1 - expect(seeds.first.stage[:name]).to eq 'test' - expect(seeds.first.builds.dig(0, :name)).to eq 'spinach' - end + describe '#stages_attributes' do + let(:config) do + YAML.dump( + rspec: { script: 'rspec', stage: 'test', only: ['branches'] }, + prod: { script: 'cap prod', stage: 'deploy', only: ['tags'] } + ) end - context 'when kubernetes policy is specified' do - let(:config) do - YAML.dump( - spinach: { stage: 'test', script: 'spinach' }, - production: { - stage: 'deploy', - script: 'cap', - only: { kubernetes: 'active' } - } - ) - end - - context 'when kubernetes is active' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do - it 'returns seeds for kubernetes dependent job' do - seeds = subject.stage_seeds(pipeline) - - expect(seeds.size).to eq 2 - expect(seeds.first.builds.dig(0, :name)).to eq 'spinach' - expect(seeds.second.builds.dig(0, :name)).to eq 'production' - end - end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - let(:pipeline) { create(:ci_empty_pipeline, project: project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - let(:pipeline) { create(:ci_empty_pipeline, project: project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - end - - context 'when kubernetes is not active' do - it 'does not return seeds for kubernetes dependent job' do - seeds = subject.stage_seeds(pipeline) - - expect(seeds.size).to eq 1 - expect(seeds.first.builds.dig(0, :name)).to eq 'spinach' - end - end + let(:attributes) do + [{ name: "build", + index: 0, + builds: [] }, + { name: "test", + index: 1, + builds: + [{ stage_idx: 1, + stage: "test", + commands: "rspec", + tag_list: [], + name: "rspec", + allow_failure: false, + when: "on_success", + environment: nil, + coverage_regex: nil, + yaml_variables: [], + options: { script: ["rspec"] }, + only: { refs: ["branches"] }, + except: {} }] }, + { name: "deploy", + index: 2, + builds: + [{ stage_idx: 2, + stage: "deploy", + commands: "cap prod", + tag_list: [], + name: "prod", + allow_failure: false, + when: "on_success", + environment: nil, + coverage_regex: nil, + yaml_variables: [], + options: { script: ["cap prod"] }, + only: { refs: ["tags"] }, + except: {} }] }] + end + + it 'returns stages seed attributes' do + expect(subject.stages_attributes).to eq attributes end end - describe "#pipeline_stage_builds" do - let(:type) { 'test' } - - it "returns builds if no branch specified" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec" } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1) - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).first).to eq({ - stage: "test", - stage_idx: 1, - name: "rspec", - commands: "pwd\nrspec", - coverage_regex: nil, - tag_list: [], - options: { - before_script: ["pwd"], - script: ["rspec"] - }, - allow_failure: false, - when: "on_success", - environment: nil, - yaml_variables: [] - }) - end - - describe 'only' do - it "does not return builds if only has another branch" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", only: ["deploy"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(0) - end - - it "does not return builds if only has regexp with another branch" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", only: ["/^deploy$/"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(0) - end - - it "returns builds if only has specified this branch" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", only: ["master"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1) - end - - it "returns builds if only has a list of branches including specified" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, only: %w(master deploy) } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1) - end - - it "returns builds if only has a branches keyword specified" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, only: ["branches"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1) - end - - it "does not return builds if only has a tags keyword" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, only: ["tags"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0) - end - - it "returns builds if only has special keywords specified and source matches" do - possibilities = [{ keyword: 'pushes', source: 'push' }, - { keyword: 'web', source: 'web' }, - { keyword: 'triggers', source: 'trigger' }, - { keyword: 'schedules', source: 'schedule' }, - { keyword: 'api', source: 'api' }, - { keyword: 'external', source: 'external' }] - - possibilities.each do |possibility| - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, only: [possibility[:keyword]] } - }) + describe 'only / except policies validations' do + context 'when `only` has an invalid value' do + let(:config) { { rspec: { script: "rspec", type: "test", only: only } } } + let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } - config_processor = Gitlab::Ci::YamlProcessor.new(config) + context 'when it is integer' do + let(:only) { 1 } - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(1) + it do + expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, + 'jobs:rspec:only has to be either an array of conditions or a hash') end end - it "does not return builds if only has special keywords specified and source doesn't match" do - possibilities = [{ keyword: 'pushes', source: 'web' }, - { keyword: 'web', source: 'push' }, - { keyword: 'triggers', source: 'schedule' }, - { keyword: 'schedules', source: 'external' }, - { keyword: 'api', source: 'trigger' }, - { keyword: 'external', source: 'api' }] - - possibilities.each do |possibility| - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, only: [possibility[:keyword]] } - }) + context 'when it is an array of integers' do + let(:only) { [1, 1] } - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(0) + it do + expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, + 'jobs:rspec:only config should be an array of strings or regexps') end end - it "returns builds if only has current repository path" do - seed_pipeline = pipeline(ref: 'deploy') - - config = YAML.dump({ - before_script: ["pwd"], - rspec: { - script: "rspec", - type: type, - only: ["branches@#{seed_pipeline.project_full_path}"] - } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, seed_pipeline).size).to eq(1) - end - - it "does not return builds if only has different repository path" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, only: ["branches@fork"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0) - end - - it "returns build only for specified type" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: "test", only: %w(master deploy) }, - staging: { script: "deploy", type: "deploy", only: %w(master deploy) }, - production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds("deploy", pipeline(ref: "deploy")).size).to eq(2) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "deploy")).size).to eq(1) - expect(config_processor.pipeline_stage_builds("deploy", pipeline(ref: "master")).size).to eq(1) - end - - context 'for invalid value' do - let(:config) { { rspec: { script: "rspec", type: "test", only: only } } } - let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } - - context 'when it is integer' do - let(:only) { 1 } - - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:only has to be either an array of conditions or a hash') - end - end - - context 'when it is an array of integers' do - let(:only) { [1, 1] } + context 'when it is invalid regex' do + let(:only) { ["/*invalid/"] } - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:only config should be an array of strings or regexps') - end - end - - context 'when it is invalid regex' do - let(:only) { ["/*invalid/"] } - - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:only config should be an array of strings or regexps') - end + it do + expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, + 'jobs:rspec:only config should be an array of strings or regexps') end end end - describe 'except' do - it "returns builds if except has another branch" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", except: ["deploy"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1) - end - - it "returns builds if except has regexp with another branch" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", except: ["/^deploy$/"] } - }) + context 'when `except` has an invalid value' do + let(:config) { { rspec: { script: "rspec", except: except } } } + let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(1) - end - - it "does not return builds if except has specified this branch" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", except: ["master"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "master")).size).to eq(0) - end - - it "does not return builds if except has a list of branches including specified" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, except: %w(master deploy) } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) + context 'when it is integer' do + let(:except) { 1 } - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0) - end - - it "does not return builds if except has a branches keyword specified" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, except: ["branches"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(0) - end - - it "returns builds if except has a tags keyword" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, except: ["tags"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1) - end - - it "does not return builds if except has special keywords specified and source matches" do - possibilities = [{ keyword: 'pushes', source: 'push' }, - { keyword: 'web', source: 'web' }, - { keyword: 'triggers', source: 'trigger' }, - { keyword: 'schedules', source: 'schedule' }, - { keyword: 'api', source: 'api' }, - { keyword: 'external', source: 'external' }] - - possibilities.each do |possibility| - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, except: [possibility[:keyword]] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(0) + it do + expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, + 'jobs:rspec:except has to be either an array of conditions or a hash') end end - it "returns builds if except has special keywords specified and source doesn't match" do - possibilities = [{ keyword: 'pushes', source: 'web' }, - { keyword: 'web', source: 'push' }, - { keyword: 'triggers', source: 'schedule' }, - { keyword: 'schedules', source: 'external' }, - { keyword: 'api', source: 'trigger' }, - { keyword: 'external', source: 'api' }] - - possibilities.each do |possibility| - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, except: [possibility[:keyword]] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) + context 'when it is an array of integers' do + let(:except) { [1, 1] } - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: 'deploy', tag: false, source: possibility[:source])).size).to eq(1) + it do + expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, + 'jobs:rspec:except config should be an array of strings or regexps') end end - it "does not return builds if except has current repository path" do - seed_pipeline = pipeline(ref: 'deploy') + context 'when it is invalid regex' do + let(:except) { ["/*invalid/"] } - config = YAML.dump({ - before_script: ["pwd"], - rspec: { - script: "rspec", - type: type, - except: ["branches@#{seed_pipeline.project_full_path}"] - } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, seed_pipeline).size).to eq(0) - end - - it "returns builds if except has different repository path" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, except: ["branches@fork"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds(type, pipeline(ref: "deploy")).size).to eq(1) - end - - it "returns build except specified type" do - master_pipeline = pipeline(ref: 'master') - test_pipeline = pipeline(ref: 'test') - deploy_pipeline = pipeline(ref: 'deploy') - - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@#{test_pipeline.project_full_path}"] }, - staging: { script: "deploy", type: "deploy", except: ["master"] }, - production: { script: "deploy", type: "deploy", except: ["master@#{master_pipeline.project_full_path}"] } - }) - - config_processor = Gitlab::Ci::YamlProcessor.new(config) - - expect(config_processor.pipeline_stage_builds("deploy", deploy_pipeline).size).to eq(2) - expect(config_processor.pipeline_stage_builds("test", test_pipeline).size).to eq(0) - expect(config_processor.pipeline_stage_builds("deploy", master_pipeline).size).to eq(0) - end - - context 'for invalid value' do - let(:config) { { rspec: { script: "rspec", except: except } } } - let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } - - context 'when it is integer' do - let(:except) { 1 } - - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:except has to be either an array of conditions or a hash') - end - end - - context 'when it is an array of integers' do - let(:except) { [1, 1] } - - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:except config should be an array of strings or regexps') - end - end - - context 'when it is invalid regex' do - let(:except) { ["/*invalid/"] } - - it do - expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, - 'jobs:rspec:except config should be an array of strings or regexps') - end + it do + expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, + 'jobs:rspec:except config should be an array of strings or regexps') end end end @@ -620,7 +254,7 @@ module Gitlab let(:config_data) { YAML.dump(config) } let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config_data) } - subject { config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first } + subject { config_processor.stage_builds_attributes('test').first } describe "before_script" do context "in global context" do @@ -703,8 +337,8 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({ + expect(config_processor.stage_builds_attributes("test").size).to eq(1) + expect(config_processor.stage_builds_attributes("test").first).to eq({ stage: "test", stage_idx: 1, name: "rspec", @@ -738,8 +372,8 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({ + expect(config_processor.stage_builds_attributes("test").size).to eq(1) + expect(config_processor.stage_builds_attributes("test").first).to eq({ stage: "test", stage_idx: 1, name: "rspec", @@ -771,8 +405,8 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({ + expect(config_processor.stage_builds_attributes("test").size).to eq(1) + expect(config_processor.stage_builds_attributes("test").first).to eq({ stage: "test", stage_idx: 1, name: "rspec", @@ -800,8 +434,8 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({ + expect(config_processor.stage_builds_attributes("test").size).to eq(1) + expect(config_processor.stage_builds_attributes("test").first).to eq({ stage: "test", stage_idx: 1, name: "rspec", @@ -946,8 +580,8 @@ module Gitlab }) config_processor = Gitlab::Ci::YamlProcessor.new(config) + builds = config_processor.stage_builds_attributes("test") - builds = config_processor.pipeline_stage_builds("test", pipeline(ref: "master")) expect(builds.size).to eq(1) expect(builds.first[:when]).to eq(when_state) end @@ -978,8 +612,8 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first[:options][:cache]).to eq( + expect(config_processor.stage_builds_attributes("test").size).to eq(1) + expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, key: 'key', @@ -997,8 +631,8 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first[:options][:cache]).to eq( + expect(config_processor.stage_builds_attributes("test").size).to eq(1) + expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, key: 'key', @@ -1017,8 +651,8 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first[:options][:cache]).to eq( + expect(config_processor.stage_builds_attributes("test").size).to eq(1) + expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq( paths: ["test/"], untracked: false, key: 'local', @@ -1046,8 +680,8 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).size).to eq(1) - expect(config_processor.pipeline_stage_builds("test", pipeline(ref: "master")).first).to eq({ + expect(config_processor.stage_builds_attributes("test").size).to eq(1) + expect(config_processor.stage_builds_attributes("test").first).to eq({ stage: "test", stage_idx: 1, name: "rspec", @@ -1083,8 +717,8 @@ module Gitlab }) config_processor = Gitlab::Ci::YamlProcessor.new(config) + builds = config_processor.stage_builds_attributes("test") - builds = config_processor.pipeline_stage_builds("test", pipeline(ref: "master")) expect(builds.size).to eq(1) expect(builds.first[:options][:artifacts][:when]).to eq(when_state) end @@ -1099,7 +733,7 @@ module Gitlab end let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) } - let(:builds) { processor.pipeline_stage_builds('deploy', pipeline(ref: 'master')) } + let(:builds) { processor.stage_builds_attributes('deploy') } context 'when a production environment is specified' do let(:environment) { 'production' } @@ -1256,7 +890,7 @@ module Gitlab describe "Hidden jobs" do let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) } - subject { config_processor.pipeline_stage_builds("test", pipeline(ref: "master")) } + subject { config_processor.stage_builds_attributes("test") } shared_examples 'hidden_job_handling' do it "doesn't create jobs that start with dot" do @@ -1304,7 +938,7 @@ module Gitlab describe "YAML Alias/Anchor" do let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) } - subject { config_processor.pipeline_stage_builds("build", pipeline(ref: "master")) } + subject { config_processor.stage_builds_attributes("build") } shared_examples 'job_templates_handling' do it "is correctly supported for jobs" do @@ -1344,13 +978,13 @@ module Gitlab context 'when template is a job' do let(:config) do - <<EOT -job1: &JOBTMPL - stage: build - script: execute-script-for-job + <<~EOT + job1: &JOBTMPL + stage: build + script: execute-script-for-job -job2: *JOBTMPL -EOT + job2: *JOBTMPL + EOT end it_behaves_like 'job_templates_handling' @@ -1358,15 +992,15 @@ EOT context 'when template is a hidden job' do let(:config) do - <<EOT -.template: &JOBTMPL - stage: build - script: execute-script-for-job + <<~EOT + .template: &JOBTMPL + stage: build + script: execute-script-for-job -job1: *JOBTMPL + job1: *JOBTMPL -job2: *JOBTMPL -EOT + job2: *JOBTMPL + EOT end it_behaves_like 'job_templates_handling' @@ -1374,18 +1008,18 @@ EOT context 'when job adds its own keys to a template definition' do let(:config) do - <<EOT -.template: &JOBTMPL - stage: build - -job1: - <<: *JOBTMPL - script: execute-script-for-job - -job2: - <<: *JOBTMPL - script: execute-script-for-job -EOT + <<~EOT + .template: &JOBTMPL + stage: build + + job1: + <<: *JOBTMPL + script: execute-script-for-job + + job2: + <<: *JOBTMPL + script: execute-script-for-job + EOT end it_behaves_like 'job_templates_handling' @@ -1677,6 +1311,14 @@ EOT Gitlab::Ci::YamlProcessor.new(config) end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings") end + + it 'returns errors if pipeline variables expression is invalid' do + config = YAML.dump({ rspec: { script: 'test', only: { variables: ['== null'] } } }) + + expect { Gitlab::Ci::YamlProcessor.new(config) } + .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, + 'jobs:rspec:only variables invalid expression syntax') + end end describe "Validate configuration templates" do @@ -1724,10 +1366,6 @@ EOT it { is_expected.to be_nil } end end - - def pipeline(**attributes) - build_stubbed(:ci_empty_pipeline, **attributes) - end end end end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index a41b7f4e104..280f799f2ab 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -1211,4 +1211,33 @@ describe Gitlab::Database::MigrationHelpers do expect(model.perform_background_migration_inline?).to eq(false) end end + + describe '#index_exists_by_name?' do + it 'returns true if an index exists' do + expect(model.index_exists_by_name?(:projects, 'index_projects_on_path')) + .to be_truthy + end + + it 'returns false if the index does not exist' do + expect(model.index_exists_by_name?(:projects, 'this_does_not_exist')) + .to be_falsy + end + + context 'when an index with a function exists', :postgresql do + before do + ActiveRecord::Base.connection.execute( + 'CREATE INDEX test_index ON projects (LOWER(path));' + ) + end + + after do + 'DROP INDEX IF EXISTS test_index;' + end + + it 'returns true if an index exists' do + expect(model.index_exists_by_name?(:projects, 'test_index')) + .to be_truthy + end + end + end end diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index 83d431a7458..e68c9850f6b 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -161,6 +161,11 @@ describe Gitlab::EncodingHelper do 'removes invalid bytes from ASCII-8bit encoded multibyte string.', "Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg".force_encoding('ASCII-8BIT'), "Lorem ipsum\n dolor sit amet, xyà yùabcdùefg" + ], + [ + 'handles UTF-16BE encoded strings', + "\xFE\xFF\x00\x41".force_encoding('ASCII-8BIT'), # An "A" prepended with UTF-16 BOM + "\xEF\xBB\xBFA" # An "A" prepended with UTF-8 BOM ] ].each do |description, test_string, xpect| it description do diff --git a/spec/lib/gitlab/git/checksum_spec.rb b/spec/lib/gitlab/git/checksum_spec.rb new file mode 100644 index 00000000000..8ff310905bf --- /dev/null +++ b/spec/lib/gitlab/git/checksum_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe Gitlab::Git::Checksum, seed_helper: true do + let(:storage) { 'default' } + + it 'raises Gitlab::Git::Repository::NoRepository when there is no repo' do + checksum = described_class.new(storage, 'nonexistent-repo') + + expect { checksum.calculate }.to raise_error Gitlab::Git::Repository::NoRepository + end + + it 'pretends that checksum is 000000... when the repo is empty' do + FileUtils.rm_rf(File.join(SEED_STORAGE_PATH, 'empty-repo.git')) + + system(git_env, *%W(#{Gitlab.config.git.bin_path} init --bare empty-repo.git), + chdir: SEED_STORAGE_PATH, + out: '/dev/null', + err: '/dev/null') + + checksum = described_class.new(storage, 'empty-repo') + + expect(checksum.calculate).to eq '0000000000000000000000000000000000000000' + end + + it 'raises Gitlab::Git::Repository::Failure when shelling out to git return non-zero status' do + checksum = described_class.new(storage, 'gitlab-git-test') + + allow(checksum).to receive(:popen).and_return(['output', nil]) + + expect { checksum.calculate }.to raise_error Gitlab::Git::Checksum::Failure + end + + it 'calculates the checksum when there is a repo' do + checksum = described_class.new(storage, 'gitlab-git-test') + + expect(checksum.calculate).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4' + end +end diff --git a/spec/lib/gitlab/git/conflict/file_spec.rb b/spec/lib/gitlab/git/conflict/file_spec.rb new file mode 100644 index 00000000000..afed6c32af6 --- /dev/null +++ b/spec/lib/gitlab/git/conflict/file_spec.rb @@ -0,0 +1,50 @@ +# coding: utf-8 +require 'spec_helper' + +describe Gitlab::Git::Conflict::File do + let(:conflict) { { theirs: { path: 'foo', mode: 33188 }, ours: { path: 'foo', mode: 33188 } } } + let(:invalid_content) { described_class.new(nil, nil, conflict, "a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) } + let(:valid_content) { described_class.new(nil, nil, conflict, "Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) } + + describe '#lines' do + context 'when the content contains non-UTF-8 characters' do + it 'raises UnsupportedEncoding' do + expect { invalid_content.lines } + .to raise_error(described_class::UnsupportedEncoding) + end + end + + context 'when the content can be converted to UTF-8' do + it 'sets lines to the lines' do + expect(valid_content.lines).to eq([{ + full_line: 'España', + type: nil, + line_obj_index: 0, + line_old: 1, + line_new: 1 + }]) + end + + it 'sets the type to text' do + expect(valid_content.type).to eq('text') + end + end + end + + describe '#content' do + context 'when the content contains non-UTF-8 characters' do + it 'raises UnsupportedEncoding' do + expect { invalid_content.content } + .to raise_error(described_class::UnsupportedEncoding) + end + end + + context 'when the content can be converted to UTF-8' do + it 'returns a valid UTF-8 string' do + expect(valid_content.content).to eq('España') + expect(valid_content.content).to be_valid_encoding + expect(valid_content.content.encoding).to eq(Encoding::UTF_8) + end + end + end +end diff --git a/spec/lib/gitlab/git/conflict/parser_spec.rb b/spec/lib/gitlab/git/conflict/parser_spec.rb index 7b035a381f1..29a1702a1c6 100644 --- a/spec/lib/gitlab/git/conflict/parser_spec.rb +++ b/spec/lib/gitlab/git/conflict/parser_spec.rb @@ -212,13 +212,6 @@ CONFLICT .not_to raise_error end end - - context 'when the file contains non-UTF-8 characters' do - it 'raises UnsupportedEncoding' do - expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) } - .to raise_error(Gitlab::Git::Conflict::Parser::UnsupportedEncoding) - end - end end end end diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb index dfccc15a4f3..8b715d717c1 100644 --- a/spec/lib/gitlab/git/gitlab_projects_spec.rb +++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::Git::GitlabProjects do let(:tmp_repos_path) { TestEnv.repos_path } let(:repo_name) { project.disk_path + '.git' } let(:tmp_repo_path) { File.join(tmp_repos_path, repo_name) } - let(:gl_projects) { build_gitlab_projects(tmp_repos_path, repo_name) } + let(:gl_projects) { build_gitlab_projects(TestEnv::REPOS_STORAGE, repo_name) } describe '#initialize' do it { expect(gl_projects.shard_path).to eq(tmp_repos_path) } @@ -223,11 +223,12 @@ describe Gitlab::Git::GitlabProjects do end describe '#fork_repository' do + let(:dest_repos) { TestEnv::REPOS_STORAGE } let(:dest_repos_path) { tmp_repos_path } let(:dest_repo_name) { File.join('@hashed', 'aa', 'bb', 'xyz.git') } let(:dest_repo) { File.join(dest_repos_path, dest_repo_name) } - subject { gl_projects.fork_repository(dest_repos_path, dest_repo_name) } + subject { gl_projects.fork_repository(dest_repos, dest_repo_name) } before do FileUtils.mkdir_p(dest_repos_path) @@ -268,7 +269,12 @@ describe Gitlab::Git::GitlabProjects do # that is not very straight-forward so I'm leaving this test here for now till # https://gitlab.com/gitlab-org/gitlab-ce/issues/41393 is fixed. context 'different storages' do - let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), 'alternative') } + let(:dest_repos) { 'alternative' } + let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), dest_repos) } + + before do + stub_storage_settings(dest_repos => { 'path' => dest_repos_path }) + end it 'forks the repo' do is_expected.to be_truthy diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb index 143aa2218c9..6fd2b33486b 100644 --- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb +++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Git::GitmodulesParser do it 'should parse a .gitmodules file correctly' do - parser = described_class.new(<<-'GITMODULES'.strip_heredoc) + data = <<~GITMODULES [submodule "vendor/libgit2"] path = vendor/libgit2 [submodule "vendor/libgit2"] @@ -16,6 +16,7 @@ describe Gitlab::Git::GitmodulesParser do url = https://example.com/another/project GITMODULES + parser = described_class.new(data.gsub("\n", "\r\n")) modules = parser.parse expect(modules).to eq({ diff --git a/spec/lib/gitlab/git/env_spec.rb b/spec/lib/gitlab/git/hook_env_spec.rb index 03836d49518..e6aa5ad8c90 100644 --- a/spec/lib/gitlab/git/env_spec.rb +++ b/spec/lib/gitlab/git/hook_env_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' -describe Gitlab::Git::Env do +describe Gitlab::Git::HookEnv do + let(:gl_repository) { 'project-123' } + describe ".set" do context 'with RequestStore.store disabled' do before do @@ -8,9 +10,9 @@ describe Gitlab::Git::Env do end it 'does not store anything' do - described_class.set(GIT_OBJECT_DIRECTORY: 'foo') + described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo') - expect(described_class.all).to be_empty + expect(described_class.all(gl_repository)).to be_empty end end @@ -21,15 +23,19 @@ describe Gitlab::Git::Env do it 'whitelist some `GIT_*` variables and stores them using RequestStore' do described_class.set( - GIT_OBJECT_DIRECTORY: 'foo', - GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar', + gl_repository, + GIT_OBJECT_DIRECTORY_RELATIVE: 'foo', + GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: 'bar', GIT_EXEC_PATH: 'baz', PATH: '~/.bin:/bin') - expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo') - expect(described_class[:GIT_ALTERNATE_OBJECT_DIRECTORIES]).to eq('bar') - expect(described_class[:GIT_EXEC_PATH]).to be_nil - expect(described_class[:bar]).to be_nil + git_env = described_class.all(gl_repository) + + expect(git_env[:GIT_OBJECT_DIRECTORY_RELATIVE]).to eq('foo') + expect(git_env[:GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE]).to eq('bar') + expect(git_env[:GIT_EXEC_PATH]).to be_nil + expect(git_env[:PATH]).to be_nil + expect(git_env[:bar]).to be_nil end end end @@ -39,14 +45,15 @@ describe Gitlab::Git::Env do before do allow(RequestStore).to receive(:active?).and_return(true) described_class.set( - GIT_OBJECT_DIRECTORY: 'foo', - GIT_ALTERNATE_OBJECT_DIRECTORIES: ['bar']) + gl_repository, + GIT_OBJECT_DIRECTORY_RELATIVE: 'foo', + GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: ['bar']) end it 'returns an env hash' do - expect(described_class.all).to eq({ - 'GIT_OBJECT_DIRECTORY' => 'foo', - 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => ['bar'] + expect(described_class.all(gl_repository)).to eq({ + 'GIT_OBJECT_DIRECTORY_RELATIVE' => 'foo', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['bar'] }) end end @@ -56,8 +63,8 @@ describe Gitlab::Git::Env do context 'with RequestStore.store enabled' do using RSpec::Parameterized::TableSyntax - let(:key) { 'GIT_OBJECT_DIRECTORY' } - subject { described_class.to_env_hash } + let(:key) { 'GIT_OBJECT_DIRECTORY_RELATIVE' } + subject { described_class.to_env_hash(gl_repository) } where(:input, :output) do nil | nil @@ -70,7 +77,7 @@ describe Gitlab::Git::Env do with_them do before do allow(RequestStore).to receive(:active?).and_return(true) - described_class.set(key.to_sym => input) + described_class.set(gl_repository, key.to_sym => input) end it 'puts the right value in the hash' do @@ -84,47 +91,25 @@ describe Gitlab::Git::Env do end end - describe ".[]" do - context 'with RequestStore.store enabled' do - before do - allow(RequestStore).to receive(:active?).and_return(true) - end - - before do - described_class.set( - GIT_OBJECT_DIRECTORY: 'foo', - GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar') - end - - it 'returns a stored value for an existing key' do - expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo') - end - - it 'returns nil for an non-existing key' do - expect(described_class[:foo]).to be_nil - end - end - end - describe 'thread-safety' do context 'with RequestStore.store enabled' do before do allow(RequestStore).to receive(:active?).and_return(true) - described_class.set(GIT_OBJECT_DIRECTORY: 'foo') + described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo') end it 'is thread-safe' do another_thread = Thread.new do - described_class.set(GIT_OBJECT_DIRECTORY: 'bar') + described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'bar') Thread.stop - described_class[:GIT_OBJECT_DIRECTORY] + described_class.all(gl_repository)[:GIT_OBJECT_DIRECTORY_RELATIVE] end # Ensure another_thread runs first sleep 0.1 until another_thread.stop? - expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo') + expect(described_class.all(gl_repository)[:GIT_OBJECT_DIRECTORY_RELATIVE]).to eq('foo') another_thread.run expect(another_thread.value).to eq('bar') diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 54ada3e423f..5cbe2808d0b 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -120,7 +120,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe 'alternates keyword argument' do context 'with no Git env stored' do before do - allow(Gitlab::Git::Env).to receive(:all).and_return({}) + allow(Gitlab::Git::HookEnv).to receive(:all).and_return({}) end it "is passed an empty array" do @@ -132,7 +132,7 @@ describe Gitlab::Git::Repository, seed_helper: true do context 'with absolute and relative Git object dir envvars stored' do before do - allow(Gitlab::Git::Env).to receive(:all).and_return({ + allow(Gitlab::Git::HookEnv).to receive(:all).and_return({ 'GIT_OBJECT_DIRECTORY_RELATIVE' => './objects/foo', 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['./objects/bar', './objects/baz'], 'GIT_OBJECT_DIRECTORY' => 'ignored', @@ -148,22 +148,6 @@ describe Gitlab::Git::Repository, seed_helper: true do repository.rugged end end - - context 'with only absolute Git object dir envvars stored' do - before do - allow(Gitlab::Git::Env).to receive(:all).and_return({ - 'GIT_OBJECT_DIRECTORY' => 'foo', - 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => %w[bar baz], - 'GIT_OTHER' => 'another_env' - }) - end - - it "is passed the absolute object dir envvars as is" do - expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: %w[foo bar baz]) - - repository.rugged - end - end end end @@ -604,17 +588,20 @@ describe Gitlab::Git::Repository, seed_helper: true do shared_examples 'returning the right branches' do let(:head_id) { repository.rugged.head.target.oid } let(:new_branch) { head_id } + let(:utf8_branch) { 'branch-é' } before do repository.create_branch(new_branch, 'master') + repository.create_branch(utf8_branch, 'master') end after do repository.delete_branch(new_branch) + repository.delete_branch(utf8_branch) end it 'displays that branch' do - expect(repository.branch_names_contains_sha(head_id)).to include('master', new_branch) + expect(repository.branch_names_contains_sha(head_id)).to include('master', new_branch, utf8_branch) end end diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index 4e0ee206219..32ec1e029c8 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -3,17 +3,6 @@ require 'spec_helper' describe Gitlab::Git::RevList do let(:repository) { create(:project, :repository).repository.raw } let(:rev_list) { described_class.new(repository, newrev: 'newrev') } - let(:env_hash) do - { - 'GIT_OBJECT_DIRECTORY' => 'foo', - 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar' - } - end - let(:command_env) { { 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'foo:bar' } } - - before do - allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash) - end def args_for_popen(args_list) [Gitlab.config.git.bin_path, 'rev-list', *args_list] @@ -23,7 +12,7 @@ describe Gitlab::Git::RevList do params = [ args_for_popen(additional_args), repository.path, - command_env, + {}, hash_including(lazy_block: with_lazy_block ? anything : nil) ] diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 6f07e423c1b..f8f09d29c73 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -240,14 +240,21 @@ describe Gitlab::GitAccess do end shared_examples 'check_project_moved' do - it 'enqueues a redirected message' do + it 'enqueues a redirected message for pushing' do push_access_check expect(Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id)).not_to be_nil end + + it 'allows push and pull access' do + aggregate_failures do + expect { push_access_check }.not_to raise_error + expect { pull_access_check }.not_to raise_error + end + end end - describe '#check_project_moved!', :clean_gitlab_redis_shared_state do + describe '#add_project_moved_message!', :clean_gitlab_redis_shared_state do before do project.add_master(user) end @@ -261,62 +268,18 @@ describe Gitlab::GitAccess do end end - context 'when a permanent redirect and ssh protocol' do + context 'with a redirect and ssh protocol' do let(:redirected_path) { 'some/other-path' } - before do - allow_any_instance_of(Gitlab::Checks::ProjectMoved).to receive(:permanent_redirect?).and_return(true) - end - - it 'allows push and pull access' do - aggregate_failures do - expect { push_access_check }.not_to raise_error - end - end - it_behaves_like 'check_project_moved' end - context 'with a permanent redirect and http protocol' do + context 'with a redirect and http protocol' do let(:redirected_path) { 'some/other-path' } let(:protocol) { 'http' } - before do - allow_any_instance_of(Gitlab::Checks::ProjectMoved).to receive(:permanent_redirect?).and_return(true) - end - - it 'allows_push and pull access' do - aggregate_failures do - expect { push_access_check }.not_to raise_error - end - end - it_behaves_like 'check_project_moved' end - - context 'with a temporal redirect and ssh protocol' do - let(:redirected_path) { 'some/other-path' } - - it 'blocks push and pull access' do - aggregate_failures do - expect { push_access_check }.to raise_error(described_class::ProjectMovedError, /Project '#{redirected_path}' was moved to '#{project.full_path}'/) - expect { push_access_check }.to raise_error(described_class::ProjectMovedError, /git remote set-url origin #{project.ssh_url_to_repo}/) - - expect { pull_access_check }.to raise_error(described_class::ProjectMovedError, /Project '#{redirected_path}' was moved to '#{project.full_path}'/) - expect { pull_access_check }.to raise_error(described_class::ProjectMovedError, /git remote set-url origin #{project.ssh_url_to_repo}/) - end - end - end - - context 'with a temporal redirect and http protocol' do - let(:redirected_path) { 'some/other-path' } - let(:protocol) { 'http' } - - it 'does not allow to push and pull access' do - expect { push_access_check }.to raise_error(described_class::ProjectMovedError, /git remote set-url origin #{project.http_url_to_repo}/) - expect { pull_access_check }.to raise_error(described_class::ProjectMovedError, /git remote set-url origin #{project.http_url_to_repo}/) - end - end end describe '#check_authentication_abilities!' do diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb index 872377c93d8..f03c7e3f04b 100644 --- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb @@ -58,4 +58,14 @@ describe Gitlab::GitalyClient::RemoteService do client.update_remote_mirror(ref_name, only_branches_matching) end end + + describe '.exists?' do + context "when the remote doesn't exist" do + let(:url) { 'https://gitlab.com/gitlab-org/ik-besta-niet-of-ik-word-geplaagd.git' } + + it 'returns false' do + expect(described_class.exists?(url)).to be(false) + end + end + end end diff --git a/spec/lib/gitlab/gitaly_client/util_spec.rb b/spec/lib/gitlab/gitaly_client/util_spec.rb index d1e0136f8c1..550db6db6d9 100644 --- a/spec/lib/gitlab/gitaly_client/util_spec.rb +++ b/spec/lib/gitlab/gitaly_client/util_spec.rb @@ -7,16 +7,19 @@ describe Gitlab::GitalyClient::Util do let(:gl_repository) { 'project-1' } let(:git_object_directory) { '.git/objects' } let(:git_alternate_object_directory) { ['/dir/one', '/dir/two'] } + let(:git_env) do + { + 'GIT_OBJECT_DIRECTORY_RELATIVE' => git_object_directory, + 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => git_alternate_object_directory + } + end subject do described_class.repository(repository_storage, relative_path, gl_repository) end it 'creates a Gitaly::Repository with the given data' do - allow(Gitlab::Git::Env).to receive(:[]).with('GIT_OBJECT_DIRECTORY_RELATIVE') - .and_return(git_object_directory) - allow(Gitlab::Git::Env).to receive(:[]).with('GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE') - .and_return(git_alternate_object_directory) + allow(Gitlab::Git::HookEnv).to receive(:all).with(gl_repository).and_return(git_env) expect(subject).to be_a(Gitaly::Repository) expect(subject.storage_name).to eq(repository_storage) diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb index 5bedfc79dd3..879b1d9fb0f 100644 --- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do :project, import_url: 'foo.git', import_source: 'foo/bar', - repository_storage_path: 'foo', + repository_storage: 'foo', disk_path: 'foo', repository: repository, create_wiki: true @@ -38,8 +38,12 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do expect(project) .to receive(:wiki_repository_exists?) .and_return(false) + expect(Gitlab::GitalyClient::RemoteService) + .to receive(:exists?) + .with("foo.wiki.git") + .and_return(true) - expect(importer.import_wiki?).to eq(true) + expect(importer.import_wiki?).to be(true) end it 'returns false if the GitHub wiki is disabled' do diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb index 4c1ca4349ea..9dcf272d25e 100644 --- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb +++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do let(:storages_paths) do { - default: { path: tmp_dir } + default: Gitlab::GitalyClient::StorageSettings.new('path' => tmp_dir) }.with_indifferent_access end @@ -56,7 +56,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do context 'storage points to not existing folder' do let(:storages_paths) do { - default: { path: 'tmp/this/path/doesnt/exist' } + default: Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/this/path/doesnt/exist') }.with_indifferent_access end @@ -102,7 +102,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do context 'storage points to not existing folder' do let(:storages_paths) do { - default: { path: 'tmp/this/path/doesnt/exist' } + default: Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/this/path/doesnt/exist') }.with_indifferent_access end diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb new file mode 100644 index 00000000000..d0dadfa78da --- /dev/null +++ b/spec/lib/gitlab/http_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Gitlab::HTTP do + describe 'allow_local_requests_from_hooks_and_services is' do + before do + WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success') + end + + context 'disabled' do + before do + allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(false) + end + + it 'deny requests to localhost' do + expect { described_class.get('http://localhost:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError) + end + + it 'deny requests to private network' do + expect { described_class.get('http://192.168.1.2:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError) + end + + context 'if allow_local_requests set to true' do + it 'override the global value and allow requests to localhost or private network' do + expect { described_class.get('http://localhost:3003', allow_local_requests: true) }.not_to raise_error + end + end + end + + context 'enabled' do + before do + allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(true) + end + + it 'allow requests to localhost' do + expect { described_class.get('http://localhost:3003') }.not_to raise_error + end + + it 'allow requests to private network' do + expect { described_class.get('http://192.168.1.2:3003') }.not_to raise_error + end + + context 'if allow_local_requests set to false' do + it 'override the global value and ban requests to localhost or private network' do + expect { described_class.get('http://localhost:3003', allow_local_requests: false) }.to raise_error(Gitlab::HTTP::BlockedUrlError) + end + end + end + end +end diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb new file mode 100644 index 00000000000..ed54d87de4a --- /dev/null +++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb @@ -0,0 +1,104 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do + let!(:service) { described_class.new } + let!(:project) { create(:project, :with_export) } + let(:shared) { project.import_export_shared } + let!(:user) { create(:user) } + + describe '#execute' do + before do + allow(service).to receive(:strategy_execute) + end + + it 'returns if project exported file is not found' do + allow(project).to receive(:export_project_path).and_return(nil) + + expect(service).not_to receive(:strategy_execute) + + service.execute(user, project) + end + + it 'creates a lock file in the export dir' do + allow(service).to receive(:delete_after_export_lock) + + service.execute(user, project) + + expect(lock_path_exist?).to be_truthy + end + + context 'when the method succeeds' do + it 'removes the lock file' do + service.execute(user, project) + + expect(lock_path_exist?).to be_falsey + end + end + + context 'when the method fails' do + before do + allow(service).to receive(:strategy_execute).and_call_original + end + + context 'when validation fails' do + before do + allow(service).to receive(:invalid?).and_return(true) + end + + it 'does not create the lock file' do + expect(service).not_to receive(:create_or_update_after_export_lock) + + service.execute(user, project) + end + + it 'does not execute main logic' do + expect(service).not_to receive(:strategy_execute) + + service.execute(user, project) + end + + it 'logs validation errors in shared context' do + expect(service).to receive(:log_validation_errors) + + service.execute(user, project) + end + end + + context 'when an exception is raised' do + it 'removes the lock' do + expect { service.execute(user, project) }.to raise_error(NotImplementedError) + + expect(lock_path_exist?).to be_falsey + end + end + end + end + + describe '#log_validation_errors' do + it 'add the message to the shared context' do + errors = %w(test_message test_message2) + + allow(service).to receive(:invalid?).and_return(true) + allow(service.errors).to receive(:full_messages).and_return(errors) + + expect(shared).to receive(:add_error_message).twice.and_call_original + + service.execute(user, project) + + expect(shared.errors).to eq errors + end + end + + describe '#to_json' do + it 'adds the current strategy class to the serialized attributes' do + params = { param1: 1 } + result = params.merge(klass: described_class.to_s).to_json + + expect(described_class.new(params).to_json).to eq result + end + end + + def lock_path_exist? + File.exist?(described_class.lock_file_path(project)) + end +end diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb new file mode 100644 index 00000000000..5fe57d9987b --- /dev/null +++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do + let(:example_url) { 'http://www.example.com' } + let(:strategy) { subject.new(url: example_url, http_method: 'post') } + let!(:project) { create(:project, :with_export) } + let!(:user) { build(:user) } + + subject { described_class } + + describe 'validations' do + it 'only POST and PUT method allowed' do + %w(POST post PUT put).each do |method| + expect(subject.new(url: example_url, http_method: method)).to be_valid + end + + expect(subject.new(url: example_url, http_method: 'whatever')).not_to be_valid + end + + it 'onyl allow urls as upload urls' do + expect(subject.new(url: example_url)).to be_valid + expect(subject.new(url: 'whatever')).not_to be_valid + end + end + + describe '#execute' do + it 'removes the exported project file after the upload' do + allow(strategy).to receive(:send_file) + allow(strategy).to receive(:handle_response_error) + + expect(project).to receive(:remove_exported_project_file) + + strategy.execute(user, project) + end + end +end diff --git a/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb new file mode 100644 index 00000000000..bf727285a9f --- /dev/null +++ b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::AfterExportStrategyBuilder do + let!(:strategies_namespace) { 'Gitlab::ImportExport::AfterExportStrategies' } + + describe '.build!' do + context 'when klass param is' do + it 'null it returns the default strategy' do + expect(described_class.build!(nil).class).to eq described_class.default_strategy + end + + it 'not a valid class it raises StrategyNotFoundError exception' do + expect { described_class.build!('Whatever') }.to raise_error(described_class::StrategyNotFoundError) + end + + it 'not a descendant of AfterExportStrategy' do + expect { described_class.build!('User') }.to raise_error(described_class::StrategyNotFoundError) + end + end + + it 'initializes strategy with attributes param' do + params = { param1: 1, param2: 2, param3: 3 } + + strategy = described_class.build!("#{strategies_namespace}::DownloadNotificationStrategy", params) + + params.each { |k, v| expect(strategy.public_send(k)).to eq v } + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index a204a8f1ffe..b675d5dc031 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -18,6 +18,7 @@ issues: - metrics - timelogs - issue_assignees +- closed_by events: - author - project diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 0b938892da5..f949a23ffbb 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -15,6 +15,7 @@ Issue: - updated_by_id - confidential - closed_at +- closed_by_id - due_date - moved_to_id - lock_version @@ -265,7 +266,9 @@ CommitStatus: - target_url - description - artifacts_file +- artifacts_file_store - artifacts_metadata +- artifacts_metadata_store - erased_by_id - erased_at - artifacts_expire_at @@ -458,6 +461,7 @@ Project: - merge_requests_ff_only_enabled - merge_requests_rebase_enabled - jobs_cache_index +- pages_https_only Author: - name ProjectFeature: diff --git a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb index 6721e02fb85..61eb059a731 100644 --- a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb +++ b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb @@ -38,7 +38,9 @@ describe Gitlab::Metrics::SidekiqMetricsExporter do expect(::WEBrick::HTTPServer).to have_received(:new).with( Port: port, - BindAddress: address + BindAddress: address, + Logger: anything, + AccessLog: anything ) end end diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb new file mode 100644 index 00000000000..d808b4d49e0 --- /dev/null +++ b/spec/lib/gitlab/omniauth_initializer_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe Gitlab::OmniauthInitializer do + let(:devise_config) { class_double(Devise) } + + subject { described_class.new(devise_config) } + + describe '#execute' do + it 'configures providers from array' do + generic_config = { 'name' => 'generic' } + + expect(devise_config).to receive(:omniauth).with(:generic) + + subject.execute([generic_config]) + end + + it 'allows "args" array for app_id and app_secret' do + legacy_config = { 'name' => 'legacy', 'args' => %w(123 abc) } + + expect(devise_config).to receive(:omniauth).with(:legacy, '123', 'abc') + + subject.execute([legacy_config]) + end + + it 'passes app_id and app_secret as additional arguments' do + twitter_config = { 'name' => 'twitter', 'app_id' => '123', 'app_secret' => 'abc' } + + expect(devise_config).to receive(:omniauth).with(:twitter, '123', 'abc') + + subject.execute([twitter_config]) + end + + it 'passes "args" hash as symbolized hash argument' do + hash_config = { 'name' => 'hash', 'args' => { 'custom' => 'format' } } + + expect(devise_config).to receive(:omniauth).with(:hash, custom: 'format') + + subject.execute([hash_config]) + end + + it 'configures fail_with_empty_uid for shibboleth' do + shibboleth_config = { 'name' => 'shibboleth', 'args' => {} } + + expect(devise_config).to receive(:omniauth).with(:shibboleth, fail_with_empty_uid: true) + + subject.execute([shibboleth_config]) + end + + it 'configures remote_sign_out_handler proc for authentiq' do + authentiq_config = { 'name' => 'authentiq', 'args' => {} } + + expect(devise_config).to receive(:omniauth).with(:authentiq, remote_sign_out_handler: an_instance_of(Proc)) + + subject.execute([authentiq_config]) + end + + it 'configures on_single_sign_out proc for cas3' do + cas3_config = { 'name' => 'cas3', 'args' => {} } + + expect(devise_config).to receive(:omniauth).with(:cas3, on_single_sign_out: an_instance_of(Proc)) + + subject.execute([cas3_config]) + end + end +end diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb index b8a2267f1a4..f480376acb4 100644 --- a/spec/lib/gitlab/performance_bar_spec.rb +++ b/spec/lib/gitlab/performance_bar_spec.rb @@ -25,6 +25,12 @@ describe Gitlab::PerformanceBar do expect(described_class.enabled?(nil)).to be_falsy end + it 'returns true when given user is an admin' do + user = build_stubbed(:user, :admin) + + expect(described_class.enabled?(user)).to be_truthy + end + it 'returns false when allowed_group_id is nil' do expect(described_class).to receive(:allowed_group_id).and_return(nil) diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb index 3d5b56cd5b8..548eb28fe4d 100644 --- a/spec/lib/gitlab/profiler_spec.rb +++ b/spec/lib/gitlab/profiler_spec.rb @@ -110,8 +110,8 @@ describe Gitlab::Profiler do 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) + expect(custom_logger.load_times_by_model).to eq('User' => [1.2, 1.3], + 'Project' => [10.4]) end it 'logs the backtrace, ignoring lines as appropriate' do @@ -164,4 +164,24 @@ describe Gitlab::Profiler do end end end + + describe '.log_load_times_by_model' do + it 'logs the model, query count, and time by slowest first' do + expect(null_logger).to receive(:load_times_by_model).and_return( + 'User' => [1.2, 1.3], + 'Project' => [10.4] + ) + + expect(null_logger).to receive(:info).with('Project total (1): 10.4ms') + expect(null_logger).to receive(:info).with('User total (2): 2.5ms') + + described_class.log_load_times_by_model(null_logger) + end + + it 'does nothing when called with a logger that does not have load times' do + expect(null_logger).not_to receive(:info) + + expect(described_class.log_load_times_by_model(null_logger)).to be_nil + end + end end diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index b67bcc77bd4..f030f371372 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -48,8 +48,8 @@ describe ::Gitlab::RepoPath do describe '.strip_storage_path' do before do allow(Gitlab.config.repositories).to receive(:storages).and_return({ - 'storage1' => { 'path' => '/foo' }, - 'storage2' => { 'path' => '/bar' } + 'storage1' => Gitlab::GitalyClient::StorageSettings.new('path' => '/foo'), + 'storage2' => Gitlab::GitalyClient::StorageSettings.new('path' => '/bar') }) end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 14b59c5e945..7ff2c0639ec 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Shell do allow(Project).to receive(:find).and_return(project) allow(gitlab_shell).to receive(:gitlab_projects) - .with(project.repository_storage_path, project.disk_path + '.git') + .with(project.repository_storage, project.disk_path + '.git') .and_return(gitlab_projects) end @@ -405,7 +405,7 @@ describe Gitlab::Shell do describe '#create_repository' do shared_examples '#create_repository' do let(:repository_storage) { 'default' } - let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } let(:repo_name) { 'project/path' } let(:created_path) { File.join(repository_storage_path, repo_name + '.git') } @@ -487,21 +487,21 @@ describe Gitlab::Shell do describe '#fork_repository' do subject do gitlab_shell.fork_repository( - project.repository_storage_path, + project.repository_storage, project.disk_path, - 'new/storage', + 'nfs-file05', 'fork/path' ) end it 'returns true when the command succeeds' do - expect(gitlab_projects).to receive(:fork_repository).with('new/storage', 'fork/path.git') { true } + expect(gitlab_projects).to receive(:fork_repository).with('nfs-file05', 'fork/path.git') { true } is_expected.to be_truthy end it 'return false when the command fails' do - expect(gitlab_projects).to receive(:fork_repository).with('new/storage', 'fork/path.git') { false } + expect(gitlab_projects).to receive(:fork_repository).with('nfs-file05', 'fork/path.git') { false } is_expected.to be_falsy end @@ -661,7 +661,7 @@ describe Gitlab::Shell do it 'returns true when the command succeeds' do expect(gitlab_projects).to receive(:import_project).with(import_url, timeout) { true } - result = gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, import_url) + result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url) expect(result).to be_truthy end @@ -671,7 +671,7 @@ describe Gitlab::Shell do expect(gitlab_projects).to receive(:import_project) { false } expect do - gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, import_url) + gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url) end.to raise_error(Gitlab::Shell::Error, "error") end end @@ -679,7 +679,7 @@ describe Gitlab::Shell do describe 'namespace actions' do subject { described_class.new } - let(:storage_path) { Gitlab.config.repositories.storages.default.path } + let(:storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path } describe '#add_namespace' do it 'creates a namespace' do diff --git a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb new file mode 100644 index 00000000000..fed9aeba30c --- /dev/null +++ b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Gitlab::SidekiqLogging::JSONFormatter do + let(:hash_input) { { foo: 1, bar: 'test' } } + let(:message) { 'This is a test' } + let(:timestamp) { Time.now } + + it 'wraps a Hash' do + result = subject.call('INFO', timestamp, 'my program', hash_input) + + data = JSON.parse(result) + expected_output = hash_input.stringify_keys + expected_output['severity'] = 'INFO' + expected_output['time'] = timestamp.utc.iso8601(3) + + expect(data).to eq(expected_output) + end + + it 'wraps a String' do + result = subject.call('DEBUG', timestamp, 'my string', message) + + data = JSON.parse(result) + expected_output = { + severity: 'DEBUG', + time: timestamp.utc.iso8601(3), + message: message + } + + expect(data).to eq(expected_output.stringify_keys) + end +end diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb new file mode 100644 index 00000000000..2421b1e5a1a --- /dev/null +++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe Gitlab::SidekiqLogging::StructuredLogger do + describe '#call' do + let(:timestamp) { Time.new('2018-01-01 12:00:00').utc } + let(:job) do + { + "class" => "TestWorker", + "args" => [1234, 'hello'], + "retry" => false, + "queue" => "cronjob:test_queue", + "queue_namespace" => "cronjob", + "jid" => "da883554ee4fe414012f5f42", + "created_at" => timestamp.to_f, + "enqueued_at" => timestamp.to_f + } + end + let(:logger) { double() } + let(:start_payload) do + job.merge( + 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start', + 'job_status' => 'start', + 'pid' => Process.pid, + 'created_at' => timestamp.iso8601(3), + 'enqueued_at' => timestamp.iso8601(3) + ) + end + let(:end_payload) do + start_payload.merge( + 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec', + 'job_status' => 'done', + 'duration' => 0.0, + "completed_at" => timestamp.iso8601(3) + ) + end + let(:exception_payload) do + end_payload.merge( + 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec', + 'job_status' => 'fail', + 'error' => ArgumentError, + 'error_message' => 'some exception' + ) + end + + before do + allow(Sidekiq).to receive(:logger).and_return(logger) + + allow(subject).to receive(:current_time).and_return(timestamp.to_f) + end + + subject { described_class.new } + + context 'with SIDEKIQ_LOG_ARGUMENTS enabled' do + before do + stub_env('SIDEKIQ_LOG_ARGUMENTS', '1') + end + + it 'logs start and end of job' do + Timecop.freeze(timestamp) do + expect(logger).to receive(:info).with(start_payload).ordered + expect(logger).to receive(:info).with(end_payload).ordered + expect(subject).to receive(:log_job_start).and_call_original + expect(subject).to receive(:log_job_done).and_call_original + + subject.call(job, 'test_queue') { } + end + end + + it 'logs an exception in job' do + Timecop.freeze(timestamp) do + expect(logger).to receive(:info).with(start_payload) + # This excludes the exception_backtrace + expect(logger).to receive(:warn).with(hash_including(exception_payload)) + expect(subject).to receive(:log_job_start).and_call_original + expect(subject).to receive(:log_job_done).and_call_original + + expect do + subject.call(job, 'test_queue') do + raise ArgumentError, 'some exception' + end + end.to raise_error(ArgumentError) + end + end + end + + context 'with SIDEKIQ_LOG_ARGUMENTS disabled' do + it 'logs start and end of job' do + Timecop.freeze(timestamp) do + start_payload.delete('args') + + expect(logger).to receive(:info).with(start_payload).ordered + expect(logger).to receive(:info).with(end_payload).ordered + expect(subject).to receive(:log_job_start).and_call_original + expect(subject).to receive(:log_job_done).and_call_original + + subject.call(job, 'test_queue') { } + end + end + end + end +end diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index d9b3c2350b1..a3b3dc3be6d 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' describe Gitlab::UrlBlocker do describe '#blocked_url?' do + let(:valid_ports) { Project::VALID_IMPORT_PORTS } + it 'allows imports from configured web host and port' do import_url = "http://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git" expect(described_class.blocked_url?(import_url)).to be false @@ -17,7 +19,7 @@ describe Gitlab::UrlBlocker do end it 'returns true for bad port' do - expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git')).to be true + expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git', valid_ports: valid_ports)).to be true end it 'returns true for alternative version of 127.0.0.1 (0177.1)' do @@ -71,6 +73,47 @@ describe Gitlab::UrlBlocker do it 'returns false for legitimate URL' do expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false end + + context 'when allow_local_network is' do + let(:local_ips) { ['192.168.1.2', '10.0.0.2', '172.16.0.2'] } + let(:fake_domain) { 'www.fakedomain.fake' } + + context 'true (default)' do + it 'does not block urls from private networks' do + local_ips.each do |ip| + stub_domain_resolv(fake_domain, ip) + + expect(described_class).not_to be_blocked_url("http://#{fake_domain}") + + unstub_domain_resolv + + expect(described_class).not_to be_blocked_url("http://#{ip}") + end + end + end + + context 'false' do + it 'blocks urls from private networks' do + local_ips.each do |ip| + stub_domain_resolv(fake_domain, ip) + + expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_local_network: false) + + unstub_domain_resolv + + expect(described_class).to be_blocked_url("http://#{ip}", allow_local_network: false) + end + end + end + + def stub_domain_resolv(domain, ip) + allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([double(ip_address: ip, ipv4_private?: true)]) + end + + def unstub_domain_resolv + allow(Addrinfo).to receive(:getaddrinfo).and_call_original + end + end end # Resolv does not support resolving UTF-8 domain names diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 138d21ede97..9e6aa109a4b 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -12,6 +12,14 @@ describe Gitlab::UsageData do create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true) create(:service, project: projects[1], type: 'SlackService', active: true) create(:service, project: projects[2], type: 'SlackService', active: true) + + gcp_cluster = create(:cluster, :provided_by_gcp) + create(:cluster, :provided_by_user) + create(:cluster, :provided_by_user, :disabled) + create(:clusters_applications_helm, :installed, cluster: gcp_cluster) + create(:clusters_applications_ingress, :installed, cluster: gcp_cluster) + create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster) + create(:clusters_applications_runner, :installed, cluster: gcp_cluster) end subject { described_class.data } @@ -64,6 +72,12 @@ describe Gitlab::UsageData do clusters clusters_enabled clusters_disabled + clusters_platforms_gke + clusters_platforms_user + clusters_applications_helm + clusters_applications_ingress + clusters_applications_prometheus + clusters_applications_runner in_review_folder groups issues @@ -97,6 +111,15 @@ describe Gitlab::UsageData do expect(count_data[:projects_jira_active]).to eq(2) expect(count_data[:projects_slack_notifications_active]).to eq(2) expect(count_data[:projects_slack_slash_active]).to eq(1) + + expect(count_data[:clusters_enabled]).to eq(6) + expect(count_data[:clusters_disabled]).to eq(1) + expect(count_data[:clusters_platforms_gke]).to eq(1) + expect(count_data[:clusters_platforms_user]).to eq(1) + expect(count_data[:clusters_applications_helm]).to eq(1) + expect(count_data[:clusters_applications_ingress]).to eq(1) + expect(count_data[:clusters_applications_prometheus]).to eq(1) + expect(count_data[:clusters_applications_runner]).to eq(1) end end diff --git a/spec/lib/gitlab/verify/lfs_objects_spec.rb b/spec/lib/gitlab/verify/lfs_objects_spec.rb index 64f3a9660e0..0f890e2c7ce 100644 --- a/spec/lib/gitlab/verify/lfs_objects_spec.rb +++ b/spec/lib/gitlab/verify/lfs_objects_spec.rb @@ -31,5 +31,21 @@ describe Gitlab::Verify::LfsObjects do expect(failures.keys).to contain_exactly(lfs_object) expect(failure.to_s).to include('Checksum mismatch') end + + context 'with remote files' do + before do + stub_lfs_object_storage + end + + it 'skips LFS objects in object storage' do + local_failure = create(:lfs_object) + create(:lfs_object, :object_storage) + + failures = {} + described_class.new(batch_size: 10).run_batches { |_, failed| failures.merge!(failed) } + + expect(failures.keys).to contain_exactly(local_failure) + end + end end end diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb index 6146ce61226..85768308edc 100644 --- a/spec/lib/gitlab/verify/uploads_spec.rb +++ b/spec/lib/gitlab/verify/uploads_spec.rb @@ -40,5 +40,21 @@ describe Gitlab::Verify::Uploads do expect(failures.keys).to contain_exactly(upload) expect(failure.to_s).to include('Checksum missing') end + + context 'with remote files' do + before do + stub_uploads_object_storage(AvatarUploader) + end + + it 'skips uploads in object storage' do + local_failure = create(:upload) + create(:upload, :object_storage) + + failures = {} + described_class.new(batch_size: 10).run_batches { |_, failed| failures.merge!(failed) } + + expect(failures.keys).to contain_exactly(local_failure) + end + end end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 37a0bf1ad36..2b3ffb2d7c0 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -55,7 +55,7 @@ describe Gitlab::Workhorse do end end - context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do + context 'when Gitaly workhorse_archive feature is disabled', :disable_gitaly do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) @@ -100,7 +100,7 @@ describe Gitlab::Workhorse do end end - context 'when Gitaly workhorse_send_git_patch feature is disabled', :skip_gitaly_mock do + context 'when Gitaly workhorse_send_git_patch feature is disabled', :disable_gitaly do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) @@ -173,7 +173,7 @@ describe Gitlab::Workhorse do end end - context 'when Gitaly workhorse_send_git_diff feature is disabled', :skip_gitaly_mock do + context 'when Gitaly workhorse_send_git_diff feature is disabled', :disable_gitaly do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) @@ -275,7 +275,7 @@ describe Gitlab::Workhorse do describe '.git_http_ok' do let(:user) { create(:user) } - let(:repo_path) { repository.path_to_repo } + let(:repo_path) { 'ignored but not allowed to be empty in gitlab-workhorse' } let(:action) { 'info_refs' } let(:params) do { @@ -455,7 +455,7 @@ describe Gitlab::Workhorse do end end - context 'when Gitaly workhorse_raw_show feature is disabled', :skip_gitaly_mock do + context 'when Gitaly workhorse_raw_show feature is disabled', :disable_gitaly do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb index 369e7b181b9..8ba15ae0f38 100644 --- a/spec/lib/mattermost/command_spec.rb +++ b/spec/lib/mattermost/command_spec.rb @@ -4,10 +4,11 @@ describe Mattermost::Command do let(:params) { { 'token' => 'token', team_id: 'abc' } } before do - Mattermost::Session.base_uri('http://mattermost.example.com') + session = Mattermost::Session.new(nil) + session.base_uri = 'http://mattermost.example.com' allow_any_instance_of(Mattermost::Client).to receive(:with_session) - .and_yield(Mattermost::Session.new(nil)) + .and_yield(session) end describe '#create' do diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb index 3db19d06305..c855643c4d8 100644 --- a/spec/lib/mattermost/session_spec.rb +++ b/spec/lib/mattermost/session_spec.rb @@ -15,7 +15,7 @@ describe Mattermost::Session, type: :request do it { is_expected.to respond_to(:strategy) } before do - described_class.base_uri(mattermost_url) + subject.base_uri = mattermost_url end describe '#with session' do diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb index 3c8206031cf..2cfa6802612 100644 --- a/spec/lib/mattermost/team_spec.rb +++ b/spec/lib/mattermost/team_spec.rb @@ -2,10 +2,11 @@ require 'spec_helper' describe Mattermost::Team do before do - Mattermost::Session.base_uri('http://mattermost.example.com') + session = Mattermost::Session.new(nil) + session.base_uri = 'http://mattermost.example.com' allow_any_instance_of(Mattermost::Client).to receive(:with_session) - .and_yield(Mattermost::Session.new(nil)) + .and_yield(session) end describe '#all' do |