summaryrefslogtreecommitdiff
path: root/spec/lib
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib')
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb2
-rw-r--r--spec/lib/backup/manager_spec.rb4
-rw-r--r--spec/lib/backup/repository_spec.rb4
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb19
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb1
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb23
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb5
-rw-r--r--spec/lib/banzai/filter/spaced_link_filter_spec.rb86
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb40
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb18
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb35
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb3
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb18
-rw-r--r--spec/lib/event_filter_spec.rb131
-rw-r--r--spec/lib/feature_spec.rb28
-rw-r--r--spec/lib/forever_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/access_spec.rb10
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_columns_spec.rb69
-rw-r--r--spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb156
-rw-r--r--spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb107
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb86
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb61
-rw-r--r--spec/lib/gitlab/ci/config/extendable/entry_spec.rb227
-rw-r--r--spec/lib/gitlab/ci/config/extendable_spec.rb228
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb285
-rw-r--r--spec/lib/gitlab/ci/external/file/local_spec.rb78
-rw-r--r--spec/lib/gitlab/ci/external/file/remote_spec.rb114
-rw-r--r--spec/lib/gitlab/ci/external/mapper_spec.rb96
-rw-r--r--spec/lib/gitlab/ci/external/processor_spec.rb182
-rw-r--r--spec/lib/gitlab/ci/parsers/test/junit_spec.rb (renamed from spec/lib/gitlab/ci/parsers/junit_spec.rb)66
-rw-r--r--spec/lib/gitlab/ci/parsers/test_spec.rb (renamed from spec/lib/gitlab/ci/parsers_spec.rb)4
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/status/build/scheduled_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/status/build/unschedule_spec.rb94
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/factory_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/scheduled_spec.rb42
-rw-r--r--spec/lib/gitlab/ci/status/scheduled_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/templates/templates_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb91
-rw-r--r--spec/lib/gitlab/cleanup/project_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb7
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb4
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb17
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb47
-rw-r--r--spec/lib/gitlab/database/subquery_spec.rb17
-rw-r--r--spec/lib/gitlab/diff/file_collection/commit_spec.rb15
-rw-r--r--spec/lib/gitlab/diff/file_collection/compare_spec.rb29
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb8
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb64
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb70
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb28
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb88
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler_spec.rb4
-rw-r--r--spec/lib/gitlab/favicon_spec.rb1
-rw-r--r--spec/lib/gitlab/file_detector_spec.rb6
-rw-r--r--spec/lib/gitlab/file_markdown_link_builder_spec.rb80
-rw-r--r--spec/lib/gitlab/file_type_detection_spec.rb82
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb17
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb8
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb31
-rw-r--r--spec/lib/gitlab/git/committer_with_hooks_spec.rb156
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb135
-rw-r--r--spec/lib/gitlab/git/diff_stats_collection_spec.rb32
-rw-r--r--spec/lib/gitlab/git/gitlab_projects_spec.rb321
-rw-r--r--spec/lib/gitlab/git/hook_env_spec.rb20
-rw-r--r--spec/lib/gitlab/git/hook_spec.rb111
-rw-r--r--spec/lib/gitlab/git/hooks_service_spec.rb50
-rw-r--r--spec/lib/gitlab/git/index_spec.rb239
-rw-r--r--spec/lib/gitlab/git/popen_spec.rb179
-rw-r--r--spec/lib/gitlab/git/push_spec.rb166
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb970
-rw-r--r--spec/lib/gitlab/git/user_spec.rb15
-rw-r--r--spec/lib/gitlab/git_access_spec.rb29
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb34
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb20
-rw-r--r--spec/lib/gitlab/gitaly_client/wiki_service_spec.rb4
-rw-r--r--spec/lib/gitlab/highlight_spec.rb17
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb105
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb31
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml10
-rw-r--r--spec/lib/gitlab/import_export/avatar_restorer_spec.rb33
-rw-r--r--spec/lib/gitlab/import_export/avatar_saver_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb89
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/importer_object_storage_spec.rb115
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb15
-rw-r--r--spec/lib/gitlab/import_export/model_configuration_spec.rb42
-rw-r--r--spec/lib/gitlab/import_export/project.json37
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb14
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb11
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml24
-rw-r--r--spec/lib/gitlab/import_export/saver_spec.rb24
-rw-r--r--spec/lib/gitlab/import_export/uploads_manager_spec.rb43
-rw-r--r--spec/lib/gitlab/import_export/uploads_restorer_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/uploads_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb35
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb167
-rw-r--r--spec/lib/gitlab/kubernetes/helm/base_command_spec.rb20
-rw-r--r--spec/lib/gitlab/kubernetes/helm/init_command_spec.rb130
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb155
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb17
-rw-r--r--spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb136
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb249
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_spec.rb24
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_token_spec.rb35
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb59
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb2
-rw-r--r--spec/lib/gitlab/null_request_store_spec.rb75
-rw-r--r--spec/lib/gitlab/patch/prependable_spec.rb234
-rw-r--r--spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/metric_group_spec.rb41
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb138
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb85
-rw-r--r--spec/lib/gitlab/safe_request_store_spec.rb245
-rw-r--r--spec/lib/gitlab/shell_spec.rb5
-rw-r--r--spec/lib/gitlab/sidekiq_throttler_spec.rb44
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb2
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb202
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb17
-rw-r--r--spec/lib/gitlab/user_extractor_spec.rb58
-rw-r--r--spec/lib/gitlab/version_info_spec.rb3
-rw-r--r--spec/lib/gitlab/web_ide_commits_counter_spec.rb19
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb16
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb47
-rw-r--r--spec/lib/mattermost/session_spec.rb4
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb12
-rw-r--r--spec/lib/quality/helm_client_spec.rb62
-rw-r--r--spec/lib/quality/kubernetes_client_spec.rb25
139 files changed, 6197 insertions, 2637 deletions
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index c73c6023b60..0a7682d906b 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -189,9 +189,9 @@ describe API::Helpers::Pagination do
it 'it returns the right link to the next page' do
allow(subject).to receive(:params)
.and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 })
+
expect_header('X-Per-Page', '2')
expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[6].id}&ks_prev_name=#{projects[6].name}&pagination=keyset&per_page=2")
-
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
end
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index ca319679e80..9633caac788 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -11,10 +11,6 @@ describe Backup::Manager do
allow(progress).to receive(:puts)
allow(progress).to receive(:print)
- allow_any_instance_of(String).to receive(:color) do |string, _color|
- string
- end
-
@old_progress = $progress # rubocop:disable Style/GlobalVars
$progress = progress # rubocop:disable Style/GlobalVars
end
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index c5a854b5660..fdeea814bb2 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -11,10 +11,6 @@ describe Backup::Repository do
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
- end
-
allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
end
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index aadfe7637dd..ba995e16be7 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -1,16 +1,21 @@
require 'spec_helper'
describe Banzai::CrossProjectReference do
- include described_class
+ let(:including_class) { Class.new.include(described_class).new }
+
+ before do
+ allow(including_class).to receive(:context).and_return({})
+ allow(including_class).to receive(:parent_from_ref).and_call_original
+ end
describe '#parent_from_ref' do
context 'when no project was referenced' do
it 'returns the project from context' do
project = double
- allow(self).to receive(:context).and_return({ project: project })
+ allow(including_class).to receive(:context).and_return({ project: project })
- expect(parent_from_ref(nil)).to eq project
+ expect(including_class.parent_from_ref(nil)).to eq project
end
end
@@ -18,15 +23,15 @@ describe Banzai::CrossProjectReference do
it 'returns the group from context' do
group = double
- allow(self).to receive(:context).and_return({ group: group })
+ allow(including_class).to receive(:context).and_return({ group: group })
- expect(parent_from_ref(nil)).to eq group
+ expect(including_class.parent_from_ref(nil)).to eq group
end
end
context 'when referenced project does not exist' do
it 'returns nil' do
- expect(parent_from_ref('invalid/reference')).to be_nil
+ expect(including_class.parent_from_ref('invalid/reference')).to be_nil
end
end
@@ -37,7 +42,7 @@ describe Banzai::CrossProjectReference do
expect(Project).to receive(:find_by_full_path)
.with('cross/reference').and_return(project2)
- expect(parent_from_ref('cross/reference')).to eq project2
+ expect(including_class.parent_from_ref('cross/reference')).to eq project2
end
end
end
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index e1af5a15371..cbff2fdab14 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -60,6 +60,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
allow(project.repository).to receive(:commit).with(commit1.id.reverse)
+ allow(project.repository).to receive(:commit).with(commit2.id)
expect(reference_filter(act).to_html).to eq exp
end
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index d9018a7e4fe..0d0554a2259 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -79,13 +79,9 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do
expect(link).to eq helper.url_for_issue(issue_id, project, only_path: true)
end
- context 'with RequestStore enabled' do
+ context 'with RequestStore enabled', :request_store do
let(:reference_filter) { HTML::Pipeline.new([described_class]) }
- before do
- allow(RequestStore).to receive(:active?).and_return(true)
- end
-
it 'queries the collection on the first call' do
expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original
expect_any_instance_of(Project).to receive(:external_issue_reference_pattern).once.and_call_original
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index a515d07b072..cf49249756a 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -40,6 +40,12 @@ describe Banzai::Filter::MarkdownFilter do
expect(result).to start_with("<pre><code>")
end
+
+ it 'works with utf8 chars in language' do
+ result = filter("```æ—¥\nsome code\n```")
+
+ expect(result).to start_with("<pre><code lang=\"æ—¥\">")
+ end
end
context 'using Redcarpet' do
@@ -60,4 +66,21 @@ describe Banzai::Filter::MarkdownFilter do
end
end
end
+
+ describe 'footnotes in tables' do
+ it 'processes footnotes in table cells' do
+ text = <<-MD.strip_heredoc
+ | Column1 |
+ | --------- |
+ | foot [^1] |
+
+ [^1]: a footnote
+ MD
+
+ result = filter(text)
+
+ expect(result).to include('<td>foot <sup')
+ expect(result).to include('<section class="footnotes">')
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index ba8dc68ceda..ed1ebe9ebf6 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -83,6 +83,11 @@ describe Banzai::Filter::RelativeLinkFilter do
expect { filter(act) }.not_to raise_error
end
+ it 'does not raise an exception with a space in the path' do
+ act = link("/uploads/d18213acd3732630991986120e167e3d/Landscape_8.jpg \nBut here's some more unexpected text :smile:)")
+ expect { filter(act) }.not_to raise_error
+ end
+
it 'ignores ref if commit is passed' do
doc = filter(link('non/existent.file'), commit: project.commit('empty-branch') )
expect(doc.at_css('a')['href'])
diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
index 4463c011522..1ad7f3ff567 100644
--- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
@@ -3,49 +3,73 @@ require 'spec_helper'
describe Banzai::Filter::SpacedLinkFilter do
include FilterSpecHelper
- let(:link) { '[example](page slug)' }
+ let(:link) { '[example](page slug)' }
+ let(:image) { '![example](img test.jpg)' }
- it 'converts slug with spaces to a link' do
- doc = filter("See #{link}")
+ context 'when a link is detected' do
+ it 'converts slug with spaces to a link' do
+ doc = filter("See #{link}")
- expect(doc.at_css('a').text).to eq 'example'
- expect(doc.at_css('a')['href']).to eq 'page%20slug'
- expect(doc.at_css('p')).to eq nil
- end
+ expect(doc.at_css('a').text).to eq 'example'
+ expect(doc.at_css('a')['href']).to eq 'page%20slug'
+ expect(doc.at_css('a')['title']).to be_nil
+ expect(doc.at_css('p')).to be_nil
+ end
- it 'converts slug with spaces and a title to a link' do
- link = '[example](page slug "title")'
- doc = filter("See #{link}")
+ it 'converts slug with spaces and a title to a link' do
+ link = '[example](page slug "title")'
+ doc = filter("See #{link}")
- expect(doc.at_css('a').text).to eq 'example'
- expect(doc.at_css('a')['href']).to eq 'page%20slug'
- expect(doc.at_css('a')['title']).to eq 'title'
- expect(doc.at_css('p')).to eq nil
- end
+ expect(doc.at_css('a').text).to eq 'example'
+ expect(doc.at_css('a')['href']).to eq 'page%20slug'
+ expect(doc.at_css('a')['title']).to eq 'title'
+ expect(doc.at_css('p')).to be_nil
+ end
- it 'does nothing when markdown_engine is redcarpet' do
- exp = act = link
- expect(filter(act, markdown_engine: :redcarpet).to_html).to eq exp
- end
+ it 'does nothing when markdown_engine is redcarpet' do
+ exp = act = link
+ expect(filter(act, markdown_engine: :redcarpet).to_html).to eq exp
+ end
+
+ it 'does nothing with empty text' do
+ link = '[](page slug)'
+ doc = filter("See #{link}")
+
+ expect(doc.at_css('a')).to be_nil
+ end
- it 'does nothing with empty text' do
- link = '[](page slug)'
- doc = filter("See #{link}")
+ it 'does nothing with an empty slug' do
+ link = '[example]()'
+ doc = filter("See #{link}")
- expect(doc.at_css('a')).to eq nil
+ expect(doc.at_css('a')).to be_nil
+ end
end
- it 'does nothing with an empty slug' do
- link = '[example]()'
- doc = filter("See #{link}")
+ context 'when an image is detected' do
+ it 'converts slug with spaces to an iamge' do
+ doc = filter("See #{image}")
+
+ expect(doc.at_css('img')['src']).to eq 'img%20test.jpg'
+ expect(doc.at_css('img')['alt']).to eq 'example'
+ expect(doc.at_css('p')).to be_nil
+ end
+
+ it 'converts slug with spaces and a title to an image' do
+ image = '![example](img test.jpg "title")'
+ doc = filter("See #{image}")
- expect(doc.at_css('a')).to eq nil
+ expect(doc.at_css('img')['src']).to eq 'img%20test.jpg'
+ expect(doc.at_css('img')['alt']).to eq 'example'
+ expect(doc.at_css('img')['title']).to eq 'title'
+ expect(doc.at_css('p')).to be_nil
+ end
end
it 'converts multiple URLs' do
link1 = '[first](slug one)'
link2 = '[second](http://example.com/slug two)'
- doc = filter("See #{link1} and #{link2}")
+ doc = filter("See #{link1} and #{image} and #{link2}")
found_links = doc.css('a')
@@ -54,6 +78,12 @@ describe Banzai::Filter::SpacedLinkFilter do
expect(found_links[0]['href']).to eq 'slug%20one'
expect(found_links[1].text).to eq 'second'
expect(found_links[1]['href']).to eq 'http://example.com/slug%20two'
+
+ found_images = doc.css('img')
+
+ expect(found_images.size).to eq(1)
+ expect(found_images[0]['src']).to eq 'img%20test.jpg'
+ expect(found_images[0]['alt']).to eq 'example'
end
described_class::IGNORE_PARENTS.each do |elem|
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index 50d053011b3..b9059b85fdc 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -7,6 +7,7 @@ describe Banzai::Filter::WikiLinkFilter do
let(:project) { build_stubbed(:project, :public, name: "wiki_link_project", namespace: namespace) }
let(:user) { double }
let(:wiki) { ProjectWiki.new(project, user) }
+ let(:repository_upload_folder) { Wikis::CreateAttachmentService::ATTACHMENT_PATH }
it "doesn't rewrite absolute links" do
filtered_link = filter("<a href='http://example.com:8000/'>Link</a>", project_wiki: wiki).children[0]
@@ -20,6 +21,45 @@ describe Banzai::Filter::WikiLinkFilter do
expect(filtered_link.attribute('href').value).to eq('/uploads/a.test')
end
+ describe "when links point to the #{Wikis::CreateAttachmentService::ATTACHMENT_PATH} folder" do
+ context 'with an "a" html tag' do
+ it 'rewrites links' do
+ filtered_link = filter("<a href='#{repository_upload_folder}/a.test'>Link</a>", project_wiki: wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.test")
+ end
+ end
+
+ context 'with "img" html tag' do
+ let(:path) { "#{wiki.wiki_base_path}/#{repository_upload_folder}/a.jpg" }
+
+ context 'inside an "a" html tag' do
+ it 'rewrites links' do
+ filtered_elements = filter("<a href='#{repository_upload_folder}/a.jpg'><img src='#{repository_upload_folder}/a.jpg'>example</img></a>", project_wiki: wiki)
+
+ expect(filtered_elements.search('img').first.attribute('src').value).to eq(path)
+ expect(filtered_elements.search('a').first.attribute('href').value).to eq(path)
+ end
+ end
+
+ context 'outside an "a" html tag' do
+ it 'rewrites links' do
+ filtered_link = filter("<img src='#{repository_upload_folder}/a.jpg'>example</img>", project_wiki: wiki).children[0]
+
+ expect(filtered_link.attribute('src').value).to eq(path)
+ end
+ end
+ end
+
+ context 'with "video" html tag' do
+ it 'rewrites links' do
+ filtered_link = filter("<video src='#{repository_upload_folder}/a.mp4'></video>", project_wiki: wiki).children[0]
+
+ expect(filtered_link.attribute('src').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.mp4")
+ end
+ end
+ end
+
describe "invalid links" do
invalid_links = ["http://:8080", "http://", "http://:8080/path"]
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 75413596431..df24cef0b8b 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -87,4 +87,22 @@ describe Banzai::Pipeline::GfmPipeline do
end
end
end
+
+ describe 'markdown link or image urls having spaces' do
+ let(:project) { create(:project, :public) }
+
+ it 'rewrites links with spaces in url' do
+ markdown = "[Link to Page](page slug)"
+ output = described_class.to_html(markdown, project: project)
+
+ expect(output).to include("href=\"page%20slug\"")
+ end
+
+ it 'rewrites images with spaces in url' do
+ markdown = "![My Image](test image.png)"
+ output = described_class.to_html(markdown, project: project)
+
+ expect(output).to include("src=\"test%20image.png\"")
+ end
+ end
end
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 88ae4c1e07a..64ca3ec345d 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -121,6 +121,13 @@ describe Banzai::Pipeline::WikiPipeline do
expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/page\"")
end
+ it 'rewrites non-file links (with spaces) to be at the scope of the wiki root' do
+ markdown = "[Link to Page](page slug)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/page%20slug\"")
+ end
+
it "rewrites file links to be at the scope of the current directory" do
markdown = "[Link to Page](page.md)"
output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
@@ -134,6 +141,13 @@ describe Banzai::Pipeline::WikiPipeline do
expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/start-page#title\"")
end
+
+ it 'rewrites links (with spaces) with anchor' do
+ markdown = '[Link to Header](start page#title)'
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/start%20page#title\"")
+ end
end
describe "when creating root links" do
@@ -164,4 +178,25 @@ describe Banzai::Pipeline::WikiPipeline do
end
end
end
+
+ describe 'videos' do
+ let(:namespace) { create(:namespace, name: "wiki_link_ns") }
+ let(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) }
+ let(:project_wiki) { ProjectWiki.new(project, double(:user)) }
+ let(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) }
+
+ it 'generates video html structure' do
+ markdown = "![video_file](video_file_name.mp4)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video_file_name.mp4"')
+ end
+
+ it 'rewrites and replaces video links names with white spaces to %20' do
+ markdown = "![video file](video file name.mp4)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video%20file%20name.mp4"')
+ end
+ end
end
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index 4e6e8eca38a..c6e9fc414a1 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -263,11 +263,10 @@ describe Banzai::ReferenceParser::BaseParser do
end
end
- context 'with RequestStore enabled' do
+ context 'with RequestStore enabled', :request_store do
before do
cache = Hash.new { |hash, key| hash[key] = {} }
- allow(RequestStore).to receive(:active?).and_return(true)
allow(subject).to receive(:collection_cache).and_return(cache)
end
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index cca53a8b9b9..f558dea209f 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -120,4 +120,22 @@ describe Banzai::ReferenceParser::CommitParser do
expect(subject.find_commits(project, %w{123})).to eq([])
end
end
+
+ context 'when checking commits on another projects' do
+ let(:control_links) do
+ [commit_link]
+ end
+
+ let(:actual_links) do
+ control_links + [commit_link, commit_link]
+ end
+
+ def commit_link
+ project = create(:project, :repository, :public)
+
+ Nokogiri::HTML.fragment(%Q{<a data-commit="#{project.commit.id}" data-project="#{project.id}"></a>}).children[0]
+ end
+
+ it_behaves_like 'no project N+1 queries'
+ end
end
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index 87ae6b6cf01..30016da6828 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -1,58 +1,119 @@
require 'spec_helper'
describe EventFilter do
+ describe 'FILTERS' do
+ it 'returns a definite list of filters' do
+ expect(described_class::FILTERS).to eq(%w[all push merged issue comments team])
+ end
+ end
+
+ describe '#filter' do
+ it 'returns "all" if given filter is nil' do
+ expect(described_class.new(nil).filter).to eq(described_class::ALL)
+ end
+
+ it 'returns "all" if given filter is ""' do
+ expect(described_class.new('').filter).to eq(described_class::ALL)
+ end
+
+ it 'returns "all" if given filter is "foo"' do
+ expect(described_class.new('foo').filter).to eq('all')
+ end
+ end
+
describe '#apply_filter' do
- let(:source_user) { create(:user) }
- let!(:public_project) { create(:project, :public) }
+ set(:public_project) { create(:project, :public) }
+
+ set(:push_event) { create(:push_event, project: public_project) }
+ set(:merged_event) { create(:event, :merged, project: public_project, target: public_project) }
+ set(:created_event) { create(:event, :created, project: public_project, target: public_project) }
+ set(:updated_event) { create(:event, :updated, project: public_project, target: public_project) }
+ set(:closed_event) { create(:event, :closed, project: public_project, target: public_project) }
+ set(:reopened_event) { create(:event, :reopened, project: public_project, target: public_project) }
+ set(:comments_event) { create(:event, :commented, project: public_project, target: public_project) }
+ set(:joined_event) { create(:event, :joined, project: public_project, target: public_project) }
+ set(:left_event) { create(:event, :left, project: public_project, target: public_project) }
- let!(:push_event) { create(:push_event, project: public_project, author: source_user) }
- let!(:merged_event) { create(:event, :merged, project: public_project, target: public_project, author: source_user) }
- let!(:created_event) { create(:event, :created, project: public_project, target: public_project, author: source_user) }
- let!(:updated_event) { create(:event, :updated, project: public_project, target: public_project, author: source_user) }
- let!(:closed_event) { create(:event, :closed, project: public_project, target: public_project, author: source_user) }
- let!(:reopened_event) { create(:event, :reopened, project: public_project, target: public_project, author: source_user) }
- let!(:comments_event) { create(:event, :commented, project: public_project, target: public_project, author: source_user) }
- let!(:joined_event) { create(:event, :joined, project: public_project, target: public_project, author: source_user) }
- let!(:left_event) { create(:event, :left, project: public_project, target: public_project, author: source_user) }
+ let(:filtered_events) { described_class.new(filter).apply_filter(Event.all) }
- it 'applies push filter' do
- events = described_class.new(described_class.push).apply_filter(Event.all)
- expect(events).to contain_exactly(push_event)
+ context 'with the "push" filter' do
+ let(:filter) { described_class::PUSH }
+
+ it 'filters push events only' do
+ expect(filtered_events).to contain_exactly(push_event)
+ end
end
- it 'applies merged filter' do
- events = described_class.new(described_class.merged).apply_filter(Event.all)
- expect(events).to contain_exactly(merged_event)
+ context 'with the "merged" filter' do
+ let(:filter) { described_class::MERGED }
+
+ it 'filters merged events only' do
+ expect(filtered_events).to contain_exactly(merged_event)
+ end
end
- it 'applies issue filter' do
- events = described_class.new(described_class.issue).apply_filter(Event.all)
- expect(events).to contain_exactly(created_event, updated_event, closed_event, reopened_event)
+ context 'with the "issue" filter' do
+ let(:filter) { described_class::ISSUE }
+
+ it 'filters issue events only' do
+ expect(filtered_events).to contain_exactly(created_event, updated_event, closed_event, reopened_event)
+ end
end
- it 'applies comments filter' do
- events = described_class.new(described_class.comments).apply_filter(Event.all)
- expect(events).to contain_exactly(comments_event)
+ context 'with the "comments" filter' do
+ let(:filter) { described_class::COMMENTS }
+
+ it 'filters comment events only' do
+ expect(filtered_events).to contain_exactly(comments_event)
+ end
end
- it 'applies team filter' do
- events = described_class.new(described_class.team).apply_filter(Event.all)
- expect(events).to contain_exactly(joined_event, left_event)
+ context 'with the "team" filter' do
+ let(:filter) { described_class::TEAM }
+
+ it 'filters team events only' do
+ expect(filtered_events).to contain_exactly(joined_event, left_event)
+ end
end
- it 'applies all filter' do
- events = described_class.new(described_class.all).apply_filter(Event.all)
- expect(events).to contain_exactly(push_event, merged_event, created_event, updated_event, closed_event, reopened_event, comments_event, joined_event, left_event)
+ context 'with the "all" filter' do
+ let(:filter) { described_class::ALL }
+
+ it 'returns all events' do
+ expect(filtered_events).to eq(Event.all)
+ end
+ end
+
+ context 'with an unknown filter' do
+ let(:filter) { 'foo' }
+
+ it 'returns all events' do
+ expect(filtered_events).to eq(Event.all)
+ end
+ end
+
+ context 'with a nil filter' do
+ let(:filter) { nil }
+
+ it 'returns all events' do
+ expect(filtered_events).to eq(Event.all)
+ end
+ end
+ end
+
+ describe '#active?' do
+ let(:event_filter) { described_class.new(described_class::TEAM) }
+
+ it 'returns false if filter does not include the given key' do
+ expect(event_filter.active?('foo')).to eq(false)
end
- it 'applies no filter' do
- events = described_class.new(nil).apply_filter(Event.all)
- expect(events).to contain_exactly(push_event, merged_event, created_event, updated_event, closed_event, reopened_event, comments_event, joined_event, left_event)
+ it 'returns false if the given key is nil' do
+ expect(event_filter.active?(nil)).to eq(false)
end
- it 'applies unknown filter' do
- events = described_class.new('').apply_filter(Event.all)
- expect(events).to contain_exactly(push_event, merged_event, created_event, updated_event, closed_event, reopened_event, comments_event, joined_event, left_event)
+ it 'returns true if filter does not include the given key' do
+ expect(event_filter.active?(described_class::TEAM)).to eq(true)
end
end
end
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 8bb5a843484..9d56c62ae57 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -91,7 +91,11 @@ describe Feature do
end
describe '.flipper' do
- shared_examples 'a memoized Flipper instance' do
+ before do
+ described_class.instance_variable_set(:@flipper, nil)
+ end
+
+ context 'when request store is inactive' do
it 'memoizes the Flipper instance' do
expect(Flipper).to receive(:new).once.and_call_original
@@ -101,16 +105,14 @@ describe Feature do
end
end
- context 'when request store is inactive' do
- before do
+ context 'when request store is active', :request_store do
+ it 'memoizes the Flipper instance' do
+ expect(Flipper).to receive(:new).once.and_call_original
+
+ described_class.flipper
described_class.instance_variable_set(:@flipper, nil)
+ described_class.flipper
end
-
- it_behaves_like 'a memoized Flipper instance'
- end
-
- context 'when request store is inactive', :request_store do
- it_behaves_like 'a memoized Flipper instance'
end
end
@@ -119,6 +121,10 @@ describe Feature do
expect(described_class.enabled?(:some_random_feature_flag)).to be_falsey
end
+ it 'returns true for undefined feature with default_enabled' do
+ expect(described_class.enabled?(:some_random_feature_flag, default_enabled: true)).to be_truthy
+ end
+
it 'returns false for existing disabled feature in the database' do
described_class.disable(:disabled_feature_flag)
@@ -160,6 +166,10 @@ describe Feature do
expect(described_class.disabled?(:some_random_feature_flag)).to be_truthy
end
+ it 'returns false for undefined feature with default_enabled' do
+ expect(described_class.disabled?(:some_random_feature_flag, default_enabled: true)).to be_falsey
+ end
+
it 'returns true for existing disabled feature in the database' do
described_class.disable(:disabled_feature_flag)
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
index cf40c467c72..494c0561975 100644
--- a/spec/lib/forever_spec.rb
+++ b/spec/lib/forever_spec.rb
@@ -7,6 +7,7 @@ describe Forever do
context 'when using PostgreSQL' do
it 'should return Postgresql future date' do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+
expect(subject).to eq(described_class::POSTGRESQL_DATE)
end
end
@@ -14,6 +15,7 @@ describe Forever do
context 'when using MySQL' do
it 'should return MySQL future date' do
allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+
expect(subject).to eq(described_class::MYSQL_DATE)
end
end
diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb
index 7800c543cdb..662f899180b 100644
--- a/spec/lib/gitlab/auth/ldap/access_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/access_spec.rb
@@ -48,7 +48,7 @@ describe Gitlab::Auth::LDAP::Access do
it 'logs the reason' do
expect(Gitlab::AppLogger).to receive(:info).with(
"LDAP account \"123456\" does not exist anymore, " \
- "blocking Gitlab user \"#{user.name}\" (#{user.email})"
+ "blocking GitLab user \"#{user.name}\" (#{user.email})"
)
access.allowed?
@@ -79,7 +79,7 @@ describe Gitlab::Auth::LDAP::Access do
it 'logs the reason' do
expect(Gitlab::AppLogger).to receive(:info).with(
"LDAP account \"123456\" is disabled in Active Directory, " \
- "blocking Gitlab user \"#{user.name}\" (#{user.email})"
+ "blocking GitLab user \"#{user.name}\" (#{user.email})"
)
access.allowed?
@@ -123,7 +123,7 @@ describe Gitlab::Auth::LDAP::Access do
it 'logs the reason' do
expect(Gitlab::AppLogger).to receive(:info).with(
"LDAP account \"123456\" is not disabled anymore, " \
- "unblocking Gitlab user \"#{user.name}\" (#{user.email})"
+ "unblocking GitLab user \"#{user.name}\" (#{user.email})"
)
access.allowed?
@@ -161,7 +161,7 @@ describe Gitlab::Auth::LDAP::Access do
it 'logs the reason' do
expect(Gitlab::AppLogger).to receive(:info).with(
"LDAP account \"123456\" does not exist anymore, " \
- "blocking Gitlab user \"#{user.name}\" (#{user.email})"
+ "blocking GitLab user \"#{user.name}\" (#{user.email})"
)
access.allowed?
@@ -183,7 +183,7 @@ describe Gitlab::Auth::LDAP::Access do
it 'logs the reason' do
expect(Gitlab::AppLogger).to receive(:info).with(
"LDAP account \"123456\" is available again, " \
- "unblocking Gitlab user \"#{user.name}\" (#{user.email})"
+ "unblocking GitLab user \"#{user.name}\" (#{user.email})"
)
access.allowed?
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
index 0735ebd6dcb..5dce3fcbcb6 100644
--- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
+++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do
+ include GitHelpers
+
let(:merge_request_diffs) { table(:merge_request_diffs) }
let(:merge_requests) { table(:merge_requests) }
@@ -9,11 +11,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m
let(:merge_request) { merge_requests.create!(iid: 1, target_project_id: project.id, source_project_id: project.id, target_branch: 'feature', source_branch: 'master').becomes(MergeRequest) }
let(:merge_request_diff) { MergeRequest.find(merge_request.id).create_merge_request_diff }
let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) }
- let(:rugged) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.rugged
- end
- end
+ let(:rugged) { rugged_repo(project.repository) }
before do
allow_any_instance_of(MergeRequestDiff)
diff --git a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
new file mode 100644
index 00000000000..2a869446753
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::EncryptColumns, :migration, schema: 20180910115836 do
+ let(:model) { Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook }
+ let(:web_hooks) { table(:web_hooks) }
+
+ let(:plaintext_attrs) do
+ {
+ 'encrypted_token' => nil,
+ 'encrypted_url' => nil,
+ 'token' => 'secret',
+ 'url' => 'http://example.com?access_token=secret'
+ }
+ end
+
+ let(:encrypted_attrs) do
+ {
+ 'encrypted_token' => be_present,
+ 'encrypted_url' => be_present,
+ 'token' => nil,
+ 'url' => nil
+ }
+ end
+
+ describe '#perform' do
+ it 'encrypts columns for the specified range' do
+ hooks = web_hooks.create([plaintext_attrs] * 5).sort_by(&:id)
+
+ # Encrypt all but the first and last rows
+ subject.perform(model, [:token, :url], hooks[1].id, hooks[3].id)
+
+ hooks = web_hooks.where(id: hooks.map(&:id)).order(:id)
+
+ aggregate_failures do
+ expect(hooks[0]).to have_attributes(plaintext_attrs)
+ expect(hooks[1]).to have_attributes(encrypted_attrs)
+ expect(hooks[2]).to have_attributes(encrypted_attrs)
+ expect(hooks[3]).to have_attributes(encrypted_attrs)
+ expect(hooks[4]).to have_attributes(plaintext_attrs)
+ end
+ end
+
+ it 'acquires an exclusive lock for the update' do
+ relation = double('relation', each: nil)
+
+ expect(model).to receive(:where) { relation }
+ expect(relation).to receive(:lock) { relation }
+
+ subject.perform(model, [:token, :url], 1, 1)
+ end
+
+ it 'skips already-encrypted columns' do
+ values = {
+ 'encrypted_token' => 'known encrypted token',
+ 'encrypted_url' => 'known encrypted url',
+ 'token' => 'token',
+ 'url' => 'url'
+ }
+
+ hook = web_hooks.create(values)
+
+ subject.perform(model, [:token, :url], hook.id, hook.id)
+
+ hook.reload
+
+ expect(hook).to have_attributes(values)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
new file mode 100644
index 00000000000..2d1505dacfe
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
@@ -0,0 +1,156 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts, :migration, schema: 20180816161409 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:jobs) { table(:ci_builds) }
+ let(:job_artifacts) { table(:ci_job_artifacts) }
+
+ subject { described_class.new.perform(*range) }
+
+ context 'when a pipeline exists' do
+ let!(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let!(:project) { projects.create!(name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id) }
+ let!(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a') }
+
+ context 'when a legacy artifacts exists' do
+ let(:artifacts_expire_at) { 1.day.since.to_s }
+ let(:file_store) { ::ObjectStorage::Store::REMOTE }
+
+ let!(:job) do
+ jobs.create!(
+ commit_id: pipeline.id,
+ project_id: project.id,
+ status: :success,
+ **artifacts_archive_attributes,
+ **artifacts_metadata_attributes)
+ end
+
+ let(:artifacts_archive_attributes) do
+ {
+ artifacts_file: 'archive.zip',
+ artifacts_file_store: file_store,
+ artifacts_size: 123,
+ artifacts_expire_at: artifacts_expire_at
+ }
+ end
+
+ let(:artifacts_metadata_attributes) do
+ {
+ artifacts_metadata: 'metadata.gz',
+ artifacts_metadata_store: file_store
+ }
+ end
+
+ it 'has legacy artifacts' do
+ expect(jobs.pluck('artifacts_file, artifacts_file_store, artifacts_size, artifacts_expire_at')).to eq([artifacts_archive_attributes.values])
+ expect(jobs.pluck('artifacts_metadata, artifacts_metadata_store')).to eq([artifacts_metadata_attributes.values])
+ end
+
+ it 'does not have new artifacts yet' do
+ expect(job_artifacts.count).to be_zero
+ end
+
+ context 'when the record exists inside of the range of a background migration' do
+ let(:range) { [job.id, job.id] }
+
+ it 'migrates a legacy artifact to ci_job_artifacts table' do
+ expect { subject }.to change { job_artifacts.count }.by(2)
+
+ expect(job_artifacts.order(:id).pluck('project_id, job_id, file_type, file_store, size, expire_at, file, file_sha256, file_location'))
+ .to eq([[project.id,
+ job.id,
+ described_class::ARCHIVE_FILE_TYPE,
+ file_store,
+ artifacts_archive_attributes[:artifacts_size],
+ artifacts_expire_at,
+ 'archive.zip',
+ nil,
+ described_class::LEGACY_PATH_FILE_LOCATION],
+ [project.id,
+ job.id,
+ described_class::METADATA_FILE_TYPE,
+ file_store,
+ nil,
+ artifacts_expire_at,
+ 'metadata.gz',
+ nil,
+ described_class::LEGACY_PATH_FILE_LOCATION]])
+
+ expect(jobs.pluck('artifacts_file, artifacts_file_store, artifacts_size, artifacts_expire_at')).to eq([[nil, nil, nil, artifacts_expire_at]])
+ expect(jobs.pluck('artifacts_metadata, artifacts_metadata_store')).to eq([[nil, nil]])
+ end
+
+ context 'when file_store is nil' do
+ let(:file_store) { nil }
+
+ it 'has nullified file_store in all legacy artifacts' do
+ expect(jobs.pluck('artifacts_file_store, artifacts_metadata_store')).to eq([[nil, nil]])
+ end
+
+ it 'fills file_store by the value of local file store' do
+ subject
+
+ expect(job_artifacts.pluck('file_store')).to all(eq(::ObjectStorage::Store::LOCAL))
+ end
+ end
+
+ context 'when new artifacts has already existed' do
+ context 'when only archive.zip existed' do
+ before do
+ job_artifacts.create!(project_id: project.id, job_id: job.id, file_type: described_class::ARCHIVE_FILE_TYPE, size: 999, file: 'archive.zip')
+ end
+
+ it 'had archive.zip already' do
+ expect(job_artifacts.exists?(job_id: job.id, file_type: described_class::ARCHIVE_FILE_TYPE)).to be_truthy
+ end
+
+ it 'migrates metadata' do
+ expect { subject }.to change { job_artifacts.count }.by(1)
+
+ expect(job_artifacts.exists?(job_id: job.id, file_type: described_class::METADATA_FILE_TYPE)).to be_truthy
+ end
+ end
+
+ context 'when both archive and metadata existed' do
+ before do
+ job_artifacts.create!(project_id: project.id, job_id: job.id, file_type: described_class::ARCHIVE_FILE_TYPE, size: 999, file: 'archive.zip')
+ job_artifacts.create!(project_id: project.id, job_id: job.id, file_type: described_class::METADATA_FILE_TYPE, size: 999, file: 'metadata.zip')
+ end
+
+ it 'does not migrate' do
+ expect { subject }.not_to change { job_artifacts.count }
+ end
+ end
+ end
+ end
+
+ context 'when the record exists outside of the range of a background migration' do
+ let(:range) { [job.id + 1, job.id + 1] }
+
+ it 'does not migrate' do
+ expect { subject }.not_to change { job_artifacts.count }
+ end
+ end
+ end
+
+ context 'when the job does not have legacy artifacts' do
+ let!(:job) { jobs.create!(commit_id: pipeline.id, project_id: project.id, status: :success) }
+
+ it 'does not have the legacy artifacts in database' do
+ expect(jobs.count).to eq(1)
+ expect(jobs.pluck('artifacts_file, artifacts_file_store, artifacts_size, artifacts_expire_at')).to eq([[nil, nil, nil, nil]])
+ expect(jobs.pluck('artifacts_metadata, artifacts_metadata_store')).to eq([[nil, nil]])
+ end
+
+ context 'when the record exists inside of the range of a background migration' do
+ let(:range) { [job.id, job.id] }
+
+ it 'does not migrate' do
+ expect { subject }.not_to change { job_artifacts.count }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
new file mode 100644
index 00000000000..c7b272cd6ca
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration, schema: 20180916011959 do
+ let(:migration) { described_class.new }
+
+ let!(:internal_pipeline) { create(:ci_pipeline, source: :web) }
+ let(:pipelines) { [internal_pipeline, unknown_pipeline].map(&:id) }
+
+ let!(:unknown_pipeline) do
+ build(:ci_pipeline, source: :unknown)
+ .tap { |pipeline| pipeline.save(validate: false) }
+ end
+
+ subject { migration.perform(pipelines.min, pipelines.max) }
+
+ shared_examples 'no changes' do
+ it 'does not change the pipeline source' do
+ expect { subject }.not_to change { unknown_pipeline.reload.source }
+ end
+ end
+
+ context 'when unknown pipeline is external' do
+ before do
+ create(:generic_commit_status, pipeline: unknown_pipeline)
+ end
+
+ it 'populates the pipeline source' do
+ subject
+
+ expect(unknown_pipeline.reload.source).to eq('external')
+ end
+
+ it 'can be repeated without effect' do
+ subject
+
+ expect { subject }.not_to change { unknown_pipeline.reload.source }
+ end
+ end
+
+ context 'when unknown pipeline has just a build' do
+ before do
+ create(:ci_build, pipeline: unknown_pipeline)
+ end
+
+ it_behaves_like 'no changes'
+ end
+
+ context 'when unknown pipeline has no statuses' do
+ it_behaves_like 'no changes'
+ end
+
+ context 'when unknown pipeline has a build and a status' do
+ before do
+ create(:generic_commit_status, pipeline: unknown_pipeline)
+ create(:ci_build, pipeline: unknown_pipeline)
+ end
+
+ it_behaves_like 'no changes'
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
new file mode 100644
index 00000000000..ab401108c84
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Policy::Changes do
+ set(:project) { create(:project) }
+
+ describe '#satisfied_by?' do
+ describe 'paths matching matching' do
+ let(:pipeline) do
+ build(:ci_empty_pipeline, project: project,
+ ref: 'master',
+ source: :push,
+ sha: '1234abcd',
+ before_sha: '0123aabb')
+ 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
+ allow(pipeline).to receive(:modified_paths) do
+ %w[some/modified/ruby/file.rb some/other_file.txt some/.dir/file]
+ end
+ end
+
+ it 'is satisfied by matching literal path' do
+ policy = described_class.new(%w[some/other_file.txt])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is satisfied by matching simple pattern' do
+ policy = described_class.new(%w[some/*.txt])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is satisfied by matching recusive pattern' do
+ policy = described_class.new(%w[some/**/*.rb])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is satisfied by matching a pattern with a dot' do
+ policy = described_class.new(%w[some/*/file])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied when pattern does not match path' do
+ policy = described_class.new(%w[some/*.rb])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied when pattern does not match' do
+ policy = described_class.new(%w[invalid/*.md])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+
+ context 'when pipelines does not run for a branch update' do
+ before do
+ pipeline.before_sha = Gitlab::Git::BLANK_SHA
+ end
+
+ it 'is always satisfied' do
+ policy = described_class.new(%w[invalid/*])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+ end
+ end
+
+ describe 'gitaly integration' do
+ set(:project) { create(:project, :repository) }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project,
+ ref: 'master',
+ source: :push,
+ sha: '498214d',
+ before_sha: '281d3a7')
+ end
+
+ let(:build) do
+ create(:ci_build, pipeline: pipeline, project: project, ref: 'master')
+ end
+
+ let(:seed) { double('build seed', to_resource: build) }
+
+ it 'is satisfied by changes introduced by a push' do
+ policy = described_class.new(['with space/*.md'])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied by changes that are not in the push' do
+ policy = described_class.new(%w[files/js/commit.js])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 6769f64f950..1169938b80c 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -1,4 +1,5 @@
-require 'spec_helper'
+require 'fast_spec_helper'
+require_dependency 'active_model'
describe Gitlab::Ci::Config::Entry::Job do
let(:entry) { described_class.new(config, name: :rspec) }
@@ -38,6 +39,14 @@ describe Gitlab::Ci::Config::Entry::Job do
expect(entry.errors).to include "job name can't be blank"
end
end
+
+ context 'when delayed job' do
+ context 'when start_in is specified' do
+ let(:config) { { script: 'echo', when: 'delayed', start_in: '1 day' } }
+
+ it { expect(entry).to be_valid }
+ end
+ end
end
context 'when entry value is not correct' do
@@ -81,6 +90,15 @@ describe Gitlab::Ci::Config::Entry::Job do
end
end
+ context 'when extends key is not a string' do
+ let(:config) { { extends: 123 } }
+
+ it 'returns error about wrong value type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include "job extends should be a string"
+ end
+ end
+
context 'when retry value is not correct' do
context 'when it is not a numeric value' do
let(:config) { { retry: true } }
@@ -119,11 +137,59 @@ describe Gitlab::Ci::Config::Entry::Job do
end
end
end
+
+ context 'when delayed job' do
+ context 'when start_in is specified' do
+ let(:config) { { script: 'echo', when: 'delayed', start_in: '1 day' } }
+
+ it 'returns error about invalid type' do
+ expect(entry).to be_valid
+ end
+ end
+
+ context 'when start_in is empty' do
+ let(:config) { { when: 'delayed', start_in: nil } }
+
+ it 'returns error about invalid type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job start in should be a duration'
+ end
+ end
+
+ context 'when start_in is not formatted as a duration' do
+ let(:config) { { when: 'delayed', start_in: 'test' } }
+
+ it 'returns error about invalid type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job start in should be a duration'
+ end
+ end
+
+ context 'when start_in is longer than one day' do
+ let(:config) { { when: 'delayed', start_in: '2 days' } }
+
+ it 'returns error about exceeding the limit' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job start in should not exceed the limit'
+ end
+ end
+ end
+
+ context 'when start_in specified without delayed specification' do
+ let(:config) { { start_in: '1 day' } }
+
+ it 'returns error about invalid type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job start in must be blank'
+ end
+ end
end
end
describe '#relevant?' do
it 'is a relevant entry' do
+ entry = described_class.new({ script: 'rspec' }, name: :rspec)
+
expect(entry).to be_relevant
end
end
@@ -226,6 +292,24 @@ describe Gitlab::Ci::Config::Entry::Job do
end
end
+ describe '#delayed?' do
+ context 'when job is a delayed' do
+ let(:config) { { script: 'deploy', when: 'delayed' } }
+
+ it 'is a delayed' do
+ expect(entry).to be_delayed
+ end
+ end
+
+ context 'when job is not a delayed' do
+ let(:config) { { script: 'deploy' } }
+
+ it 'is not a delayed' do
+ expect(entry).not_to be_delayed
+ end
+ end
+ end
+
describe '#ignored?' do
context 'when job is a manual action' do
context 'when it is not specified if job is allowed to fail' do
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index 83d39b82068..bef93fe7af7 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -1,4 +1,5 @@
-require 'spec_helper'
+require 'fast_spec_helper'
+require_dependency 'active_model'
describe Gitlab::Ci::Config::Entry::Policy do
let(:entry) { described_class.new(config) }
@@ -124,6 +125,23 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
end
+ context 'when specifying a valid changes policy' do
+ let(:config) { { changes: %w[some/* paths/**/*.rb] } }
+
+ it 'is a correct configuraton' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq(config)
+ end
+ end
+
+ context 'when changes policy is invalid' do
+ let(:config) { { changes: [1, 2] } }
+
+ it 'returns errors' do
+ expect(entry.errors).to include /changes should be an array of strings/
+ end
+ end
+
context 'when specifying unknown policy' do
let(:config) { { refs: ['master'], invalid: :something } }
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index b3a3a6bee1d..7cf541447ce 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -3,27 +3,54 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Reports do
let(:entry) { described_class.new(config) }
+ describe 'validates ALLOWED_KEYS' do
+ let(:artifact_file_types) { Ci::JobArtifact.file_types }
+
+ described_class::ALLOWED_KEYS.each do |keyword, _|
+ it "expects #{keyword} to be an artifact file_type" do
+ expect(artifact_file_types).to include(keyword)
+ end
+ end
+ end
+
describe 'validation' do
context 'when entry config value is correct' do
- let(:config) { { junit: %w[junit.xml] } }
+ using RSpec::Parameterized::TableSyntax
- describe '#value' do
- it 'returns artifacs configuration' do
- expect(entry.value).to eq config
+ shared_examples 'a valid entry' do |keyword, file|
+ describe '#value' do
+ it 'returns artifacs configuration' do
+ expect(entry.value).to eq({ "#{keyword}": [file] } )
+ end
end
- end
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
end
end
- context 'when value is not array' do
- let(:config) { { junit: 'junit.xml' } }
+ where(:keyword, :file) do
+ :junit | 'junit.xml'
+ :codequality | 'codequality.json'
+ :sast | 'gl-sast-report.json'
+ :dependency_scanning | 'gl-dependency-scanning-report.json'
+ :container_scanning | 'gl-container-scanning-report.json'
+ :dast | 'gl-dast-report.json'
+ end
+
+ with_them do
+ context 'when value is an array' do
+ let(:config) { { "#{keyword}": [file] } }
- it 'converts to array' do
- expect(entry.value).to eq({ junit: ['junit.xml'] } )
+ it_behaves_like 'a valid entry', params[:keyword], params[:file]
+ end
+
+ context 'when value is not array' do
+ let(:config) { { "#{keyword}": file } }
+
+ it_behaves_like 'a valid entry', params[:keyword], params[:file]
end
end
end
@@ -31,11 +58,13 @@ describe Gitlab::Ci::Config::Entry::Reports do
context 'when entry value is not correct' do
describe '#errors' do
context 'when value of attribute is invalid' do
- let(:config) { { junit: 10 } }
+ where(key: described_class::ALLOWED_KEYS) do
+ let(:config) { { "#{key}": 10 } }
- it 'reports error' do
- expect(entry.errors)
- .to include 'reports junit should be an array of strings or a string'
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "reports #{key} should be an array of strings or a string"
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
new file mode 100644
index 00000000000..0a148375d11
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
@@ -0,0 +1,227 @@
+require 'fast_spec_helper'
+
+describe Gitlab::Ci::Config::Extendable::Entry do
+ describe '.new' do
+ context 'when entry key is not included in the context hash' do
+ it 'raises error' do
+ expect { described_class.new(:test, something: 'something') }
+ .to raise_error StandardError, 'Invalid entry key!'
+ end
+ end
+ end
+
+ describe '#value' do
+ it 'reads a hash value from the context' do
+ entry = described_class.new(:test, test: 'something')
+
+ expect(entry.value).to eq 'something'
+ end
+ end
+
+ describe '#extensible?' do
+ context 'when entry has inheritance defined' do
+ it 'is extensible' do
+ entry = described_class.new(:test, test: { extends: 'something' })
+
+ expect(entry).to be_extensible
+ end
+ end
+
+ context 'when entry does not have inheritance specified' do
+ it 'is not extensible' do
+ entry = described_class.new(:test, test: { script: 'something' })
+
+ expect(entry).not_to be_extensible
+ end
+ end
+
+ context 'when entry value is not a hash' do
+ it 'is not extensible' do
+ entry = described_class.new(:test, test: 'something')
+
+ expect(entry).not_to be_extensible
+ end
+ end
+ end
+
+ describe '#extends_key' do
+ context 'when entry is extensible' do
+ it 'returns symbolized extends key value' do
+ entry = described_class.new(:test, test: { extends: 'something' })
+
+ expect(entry.extends_key).to eq :something
+ end
+ end
+
+ context 'when entry is not extensible' do
+ it 'returns nil' do
+ entry = described_class.new(:test, test: 'something')
+
+ expect(entry.extends_key).to be_nil
+ end
+ end
+ end
+
+ describe '#ancestors' do
+ let(:parent) do
+ described_class.new(:test, test: { extends: 'something' })
+ end
+
+ let(:child) do
+ described_class.new(:job, { job: { script: 'something' } }, parent)
+ end
+
+ it 'returns ancestors keys' do
+ expect(child.ancestors).to eq [:test]
+ end
+ end
+
+ describe '#base_hash!' do
+ subject { described_class.new(:test, hash) }
+
+ context 'when base hash is not extensible' do
+ let(:hash) do
+ {
+ template: { script: 'rspec' },
+ test: { extends: 'template' }
+ }
+ end
+
+ it 'returns unchanged base hash' do
+ expect(subject.base_hash!).to eq(script: 'rspec')
+ end
+ end
+
+ context 'when base hash is extensible too' do
+ let(:hash) do
+ {
+ first: { script: 'rspec' },
+ second: { extends: 'first' },
+ test: { extends: 'second' }
+ }
+ end
+
+ it 'extends the base hash first' do
+ expect(subject.base_hash!).to eq(extends: 'first', script: 'rspec')
+ end
+
+ it 'mutates original context' do
+ subject.base_hash!
+
+ expect(hash.fetch(:second)).to eq(extends: 'first', script: 'rspec')
+ end
+ end
+ end
+
+ describe '#extend!' do
+ subject { described_class.new(:test, hash) }
+
+ context 'when extending a non-hash value' do
+ let(:hash) do
+ {
+ first: 'my value',
+ test: { extends: 'first' }
+ }
+ end
+
+ it 'raises an error' do
+ expect { subject.extend! }
+ .to raise_error(described_class::InvalidExtensionError,
+ /invalid base hash/)
+ end
+ end
+
+ context 'when extending unknown key' do
+ let(:hash) do
+ { test: { extends: 'something' } }
+ end
+
+ it 'raises an error' do
+ expect { subject.extend! }
+ .to raise_error(described_class::InvalidExtensionError,
+ /unknown key/)
+ end
+ end
+
+ context 'when extending a hash correctly' do
+ let(:hash) do
+ {
+ first: { script: 'my value' },
+ second: { extends: 'first' },
+ test: { extends: 'second' }
+ }
+ end
+
+ let(:result) do
+ {
+ first: { script: 'my value' },
+ second: { extends: 'first', script: 'my value' },
+ test: { extends: 'second', script: 'my value' }
+ }
+ end
+
+ it 'returns extended part of the hash' do
+ expect(subject.extend!).to eq result[:test]
+ end
+
+ it 'mutates original context' do
+ subject.extend!
+
+ expect(hash).to eq result
+ end
+ end
+
+ context 'when hash is not extensible' do
+ let(:hash) do
+ {
+ first: { script: 'my value' },
+ second: { extends: 'first' },
+ test: { value: 'something' }
+ }
+ end
+
+ it 'returns original key value' do
+ expect(subject.extend!).to eq(value: 'something')
+ end
+
+ it 'does not mutate orignal context' do
+ original = hash.deep_dup
+
+ subject.extend!
+
+ expect(hash).to eq original
+ end
+ end
+
+ context 'when circular depenency gets detected' do
+ let(:hash) do
+ { test: { extends: 'test' } }
+ end
+
+ it 'raises an error' do
+ expect { subject.extend! }
+ .to raise_error(described_class::CircularDependencyError,
+ /circular dependency detected/)
+ end
+ end
+
+ context 'when nesting level is too deep' do
+ before do
+ stub_const("#{described_class}::MAX_NESTING_LEVELS", 0)
+ end
+
+ let(:hash) do
+ {
+ first: { script: 'my value' },
+ second: { extends: 'first' },
+ test: { extends: 'second' }
+ }
+ end
+
+ it 'raises an error' do
+ expect { subject.extend! }
+ .to raise_error(described_class::NestingTooDeepError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/extendable_spec.rb b/spec/lib/gitlab/ci/config/extendable_spec.rb
new file mode 100644
index 00000000000..90213f6603d
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/extendable_spec.rb
@@ -0,0 +1,228 @@
+require 'fast_spec_helper'
+
+describe Gitlab::Ci::Config::Extendable do
+ subject { described_class.new(hash) }
+
+ describe '#each' do
+ context 'when there is extendable entry in the hash' do
+ let(:test) do
+ { extends: 'something', only: %w[master] }
+ end
+
+ let(:hash) do
+ { something: { script: 'ls' }, test: test }
+ end
+
+ it 'yields control' do
+ expect { |b| subject.each(&b) }.to yield_control
+ end
+ end
+ end
+
+ describe '#to_hash' do
+ context 'when hash does not contain extensions' do
+ let(:hash) do
+ {
+ test: { script: 'test' },
+ production: {
+ script: 'deploy',
+ only: { variables: %w[$SOMETHING] }
+ }
+ }
+ end
+
+ it 'does not modify the hash' do
+ expect(subject.to_hash).to eq hash
+ end
+ end
+
+ context 'when hash has a single simple extension' do
+ let(:hash) do
+ {
+ something: {
+ script: 'deploy',
+ only: { variables: %w[$SOMETHING] }
+ },
+
+ test: {
+ extends: 'something',
+ script: 'ls',
+ only: { refs: %w[master] }
+ }
+ }
+ end
+
+ it 'extends a hash with a deep reverse merge' do
+ expect(subject.to_hash).to eq(
+ something: {
+ script: 'deploy',
+ only: { variables: %w[$SOMETHING] }
+ },
+
+ test: {
+ extends: 'something',
+ script: 'ls',
+ only: {
+ refs: %w[master],
+ variables: %w[$SOMETHING]
+ }
+ }
+ )
+ end
+ end
+
+ context 'when a hash uses recursive extensions' do
+ let(:hash) do
+ {
+ test: {
+ extends: 'something',
+ script: 'ls',
+ only: { refs: %w[master] }
+ },
+
+ build: {
+ extends: 'something',
+ stage: 'build'
+ },
+
+ deploy: {
+ stage: 'deploy',
+ extends: '.first'
+ },
+
+ something: {
+ extends: '.first',
+ script: 'exec',
+ only: { variables: %w[$SOMETHING] }
+ },
+
+ '.first': {
+ script: 'run',
+ only: { kubernetes: 'active' }
+ }
+ }
+ end
+
+ it 'extends a hash with a deep reverse merge' do
+ expect(subject.to_hash).to eq(
+ '.first': {
+ script: 'run',
+ only: { kubernetes: 'active' }
+ },
+
+ something: {
+ extends: '.first',
+ script: 'exec',
+ only: {
+ kubernetes: 'active',
+ variables: %w[$SOMETHING]
+ }
+ },
+
+ deploy: {
+ script: 'run',
+ stage: 'deploy',
+ only: { kubernetes: 'active' },
+ extends: '.first'
+ },
+
+ build: {
+ extends: 'something',
+ script: 'exec',
+ stage: 'build',
+ only: {
+ kubernetes: 'active',
+ variables: %w[$SOMETHING]
+ }
+ },
+
+ test: {
+ extends: 'something',
+ script: 'ls',
+ only: {
+ refs: %w[master],
+ variables: %w[$SOMETHING],
+ kubernetes: 'active'
+ }
+ }
+ )
+ end
+ end
+
+ context 'when nested circular dependecy has been detected' do
+ let(:hash) do
+ {
+ test: {
+ extends: 'something',
+ script: 'ls',
+ only: { refs: %w[master] }
+ },
+
+ something: {
+ extends: '.first',
+ script: 'deploy',
+ only: { variables: %w[$SOMETHING] }
+ },
+
+ '.first': {
+ extends: 'something',
+ script: 'run',
+ only: { kubernetes: 'active' }
+ }
+ }
+ end
+
+ it 'raises an error about circular dependency' do
+ expect { subject.to_hash }
+ .to raise_error(described_class::Entry::CircularDependencyError)
+ end
+ end
+
+ context 'when circular dependecy to self has been detected' do
+ let(:hash) do
+ {
+ test: {
+ extends: 'test',
+ script: 'ls',
+ only: { refs: %w[master] }
+ }
+ }
+ end
+
+ it 'raises an error about circular dependency' do
+ expect { subject.to_hash }
+ .to raise_error(described_class::Entry::CircularDependencyError)
+ end
+ end
+
+ context 'when invalid extends value is specified' do
+ let(:hash) do
+ { something: { extends: 1, script: 'ls' } }
+ end
+
+ it 'raises an error about invalid extension' do
+ expect { subject.to_hash }
+ .to raise_error(described_class::Entry::InvalidExtensionError)
+ end
+ end
+
+ context 'when extensible entry has non-hash inheritance defined' do
+ let(:hash) do
+ {
+ test: {
+ extends: 'something',
+ script: 'ls',
+ only: { refs: %w[master] }
+ },
+
+ something: 'some text'
+ }
+ end
+
+ it 'raises an error about invalid base' do
+ expect { subject.to_hash }
+ .to raise_error(described_class::Entry::InvalidExtensionError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 2e204da307d..b43aca8a354 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -1,4 +1,6 @@
-require 'spec_helper'
+require 'fast_spec_helper'
+
+require_dependency 'active_model'
describe Gitlab::Ci::Config do
let(:config) do
@@ -42,6 +44,36 @@ describe Gitlab::Ci::Config do
end
end
+ context 'when using extendable hash' do
+ let(:yml) do
+ <<-EOS
+ image: ruby:2.2
+
+ rspec:
+ script: rspec
+
+ test:
+ extends: rspec
+ image: ruby:alpine
+ EOS
+ end
+
+ it 'correctly extends the hash' do
+ hash = {
+ image: 'ruby:2.2',
+ rspec: { script: 'rspec' },
+ test: {
+ extends: 'rspec',
+ image: 'ruby:alpine',
+ script: 'rspec'
+ }
+ }
+
+ expect(config).to be_valid
+ expect(config.to_hash).to eq hash
+ end
+ end
+
context 'when config is invalid' do
context 'when yml is incorrect' do
let(:yml) { '// invalid' }
@@ -49,7 +81,7 @@ describe Gitlab::Ci::Config do
describe '.new' do
it 'raises error' do
expect { config }.to raise_error(
- ::Gitlab::Ci::Config::Loader::FormatError,
+ described_class::ConfigError,
/Invalid configuration format/
)
end
@@ -75,5 +107,254 @@ describe Gitlab::Ci::Config do
end
end
end
+
+ context 'when invalid extended hash has been provided' do
+ let(:yml) do
+ <<-EOS
+ test:
+ extends: test
+ script: rspec
+ EOS
+ end
+
+ it 'raises an error' do
+ expect { config }.to raise_error(
+ described_class::ConfigError, /circular dependency detected/
+ )
+ end
+ end
+ end
+
+ context "when using 'include' directive" do
+ let(:project) { create(:project, :repository) }
+ let(:remote_location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:local_location) { 'spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml' }
+
+ let(:remote_file_content) do
+ <<~HEREDOC
+ variables:
+ AUTO_DEVOPS_DOMAIN: domain.example.com
+ POSTGRES_USER: user
+ POSTGRES_PASSWORD: testing-password
+ POSTGRES_ENABLED: "true"
+ POSTGRES_DB: $CI_ENVIRONMENT_SLUG
+ HEREDOC
+ end
+
+ let(:local_file_content) do
+ File.read(Rails.root.join(local_location))
+ end
+
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{local_location}
+ - #{remote_location}
+
+ image: ruby:2.2
+ HEREDOC
+ end
+
+ let(:config) do
+ described_class.new(gitlab_ci_yml, project: project, sha: '12345')
+ end
+
+ before do
+ WebMock.stub_request(:get, remote_location)
+ .to_return(body: remote_file_content)
+
+ allow(project.repository)
+ .to receive(:blob_data_at).and_return(local_file_content)
+ end
+
+ context "when gitlab_ci_yml has valid 'include' defined" do
+ it 'should return a composed hash' do
+ before_script_values = [
+ "apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs", "ruby -v",
+ "which ruby",
+ "gem install bundler --no-ri --no-rdoc",
+ "bundle install --jobs $(nproc) \"${FLAGS[@]}\""
+ ]
+ variables = {
+ AUTO_DEVOPS_DOMAIN: "domain.example.com",
+ POSTGRES_USER: "user",
+ POSTGRES_PASSWORD: "testing-password",
+ POSTGRES_ENABLED: "true",
+ POSTGRES_DB: "$CI_ENVIRONMENT_SLUG"
+ }
+ composed_hash = {
+ before_script: before_script_values,
+ image: "ruby:2.2",
+ rspec: { script: ["bundle exec rspec"] },
+ variables: variables
+ }
+
+ expect(config.to_hash).to eq(composed_hash)
+ end
+ end
+
+ context "when gitlab_ci.yml has invalid 'include' defined" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include: invalid
+ HEREDOC
+ end
+
+ it 'raises error YamlProcessor validationError' do
+ expect { config }.to raise_error(
+ ::Gitlab::Ci::YamlProcessor::ValidationError,
+ "Local file 'invalid' is not valid."
+ )
+ end
+ end
+
+ describe 'external file version' do
+ context 'when external local file SHA is defined' do
+ it 'is using a defined value' do
+ expect(project.repository).to receive(:blob_data_at)
+ .with('eeff1122', local_location)
+
+ described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122')
+ end
+ end
+
+ context 'when external local file SHA is not defined' do
+ it 'is using latest SHA on the default branch' do
+ expect(project.repository).to receive(:root_ref_sha)
+
+ described_class.new(gitlab_ci_yml, project: project)
+ end
+ end
+ end
+
+ context "when both external files and gitlab_ci.yml defined the same key" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ image: ruby:2.2
+ HEREDOC
+ end
+
+ let(:remote_file_content) do
+ <<~HEREDOC
+ image: php:5-fpm-alpine
+ HEREDOC
+ end
+
+ it 'should take precedence' do
+ expect(config.to_hash).to eq({ image: 'ruby:2.2' })
+ end
+ end
+
+ context "when both external files and gitlab_ci.yml define a dictionary of distinct variables" do
+ let(:remote_file_content) do
+ <<~HEREDOC
+ variables:
+ A: 'alpha'
+ B: 'beta'
+ HEREDOC
+ end
+
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ variables:
+ C: 'gamma'
+ D: 'delta'
+ HEREDOC
+ end
+
+ it 'should merge the variables dictionaries' do
+ expect(config.to_hash).to eq({ variables: { A: 'alpha', B: 'beta', C: 'gamma', D: 'delta' } })
+ end
+ end
+
+ context "when both external files and gitlab_ci.yml define a dictionary of overlapping variables" do
+ let(:remote_file_content) do
+ <<~HEREDOC
+ variables:
+ A: 'alpha'
+ B: 'beta'
+ C: 'omnicron'
+ HEREDOC
+ end
+
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ variables:
+ C: 'gamma'
+ D: 'delta'
+ HEREDOC
+ end
+
+ it 'later declarations should take precedence' do
+ expect(config.to_hash).to eq({ variables: { A: 'alpha', B: 'beta', C: 'gamma', D: 'delta' } })
+ end
+ end
+
+ context 'when both external files and gitlab_ci.yml define a job' do
+ let(:remote_file_content) do
+ <<~HEREDOC
+ job1:
+ script:
+ - echo 'hello from remote file'
+ HEREDOC
+ end
+
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ job1:
+ variables:
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ HEREDOC
+ end
+
+ it 'merges the jobs' do
+ expect(config.to_hash).to eq({
+ job1: {
+ script: ["echo 'hello from remote file'"],
+ variables: {
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ }
+ }
+ })
+ end
+
+ context 'when the script key is in both' do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ job1:
+ script:
+ - echo 'hello from main file'
+ variables:
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ HEREDOC
+ end
+
+ it 'uses the script from the gitlab_ci.yml' do
+ expect(config.to_hash).to eq({
+ job1: {
+ script: ["echo 'hello from main file'"],
+ variables: {
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ }
+ }
+ })
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/external/file/local_spec.rb b/spec/lib/gitlab/ci/external/file/local_spec.rb
new file mode 100644
index 00000000000..73bb4ccf468
--- /dev/null
+++ b/spec/lib/gitlab/ci/external/file/local_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::External::File::Local do
+ let(:project) { create(:project, :repository) }
+ let(:local_file) { described_class.new(location, { project: project, sha: '12345' }) }
+
+ describe '#valid?' do
+ context 'when is a valid local path' do
+ let(:location) { '/lib/gitlab/ci/templates/existent-file.yml' }
+
+ before do
+ allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("image: 'ruby2:2'")
+ end
+
+ it 'should return true' do
+ expect(local_file.valid?).to be_truthy
+ end
+ end
+
+ context 'when is not a valid local path' do
+ let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
+
+ it 'should return false' do
+ expect(local_file.valid?).to be_falsy
+ end
+ end
+
+ context 'when is not a yaml file' do
+ let(:location) { '/config/application.rb' }
+
+ it 'should return false' do
+ expect(local_file.valid?).to be_falsy
+ end
+ end
+ end
+
+ describe '#content' do
+ context 'with a a valid file' do
+ let(:local_file_content) do
+ <<~HEREDOC
+ before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+ HEREDOC
+ end
+ let(:location) { '/lib/gitlab/ci/templates/existent-file.yml' }
+
+ before do
+ allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return(local_file_content)
+ end
+
+ it 'should return the content of the file' do
+ expect(local_file.content).to eq(local_file_content)
+ end
+ end
+
+ context 'with an invalid file' do
+ let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
+
+ it 'should be nil' do
+ expect(local_file.content).to be_nil
+ end
+ end
+ end
+
+ describe '#error_message' do
+ let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
+
+ it 'should return an error message' do
+ expect(local_file.error_message).to eq("Local file '#{location}' is not valid.")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/external/file/remote_spec.rb b/spec/lib/gitlab/ci/external/file/remote_spec.rb
new file mode 100644
index 00000000000..b1819c8960b
--- /dev/null
+++ b/spec/lib/gitlab/ci/external/file/remote_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::External::File::Remote do
+ let(:remote_file) { described_class.new(location) }
+ let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:remote_file_content) do
+ <<~HEREDOC
+ before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+ HEREDOC
+ end
+
+ describe "#valid?" do
+ context 'when is a valid remote url' do
+ before do
+ WebMock.stub_request(:get, location).to_return(body: remote_file_content)
+ end
+
+ it 'should return true' do
+ expect(remote_file.valid?).to be_truthy
+ end
+ end
+
+ context 'with an irregular url' do
+ let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+
+ it 'should return false' do
+ expect(remote_file.valid?).to be_falsy
+ end
+ end
+
+ context 'with a timeout' do
+ before do
+ allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error)
+ end
+
+ it 'should be falsy' do
+ expect(remote_file.valid?).to be_falsy
+ end
+ end
+
+ context 'when is not a yaml file' do
+ let(:location) { 'https://asdasdasdaj48ggerexample.com' }
+
+ it 'should be falsy' do
+ expect(remote_file.valid?).to be_falsy
+ end
+ end
+
+ context 'with an internal url' do
+ let(:location) { 'http://localhost:8080' }
+
+ it 'should be falsy' do
+ expect(remote_file.valid?).to be_falsy
+ end
+ end
+ end
+
+ describe "#content" do
+ context 'with a valid remote file' do
+ before do
+ WebMock.stub_request(:get, location).to_return(body: remote_file_content)
+ end
+
+ it 'should return the content of the file' do
+ expect(remote_file.content).to eql(remote_file_content)
+ end
+ end
+
+ context 'with a timeout' do
+ before do
+ allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error)
+ end
+
+ it 'should be falsy' do
+ expect(remote_file.content).to be_falsy
+ end
+ end
+
+ context 'with an invalid remote url' do
+ let(:location) { 'https://asdasdasdaj48ggerexample.com' }
+
+ before do
+ WebMock.stub_request(:get, location).to_raise(SocketError.new('Some HTTP error'))
+ end
+
+ it 'should be nil' do
+ expect(remote_file.content).to be_nil
+ end
+ end
+
+ context 'with an internal url' do
+ let(:location) { 'http://localhost:8080' }
+
+ it 'should be nil' do
+ expect(remote_file.content).to be_nil
+ end
+ end
+ end
+
+ describe "#error_message" do
+ let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+
+ it 'should return an error message' do
+ expect(remote_file.error_message).to eq("Remote file '#{location}' is not valid.")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/external/mapper_spec.rb b/spec/lib/gitlab/ci/external/mapper_spec.rb
new file mode 100644
index 00000000000..d925d6af73d
--- /dev/null
+++ b/spec/lib/gitlab/ci/external/mapper_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::External::Mapper do
+ let(:project) { create(:project, :repository) }
+ let(:file_content) do
+ <<~HEREDOC
+ image: 'ruby:2.2'
+ HEREDOC
+ end
+
+ describe '#process' do
+ subject { described_class.new(values, project, '123456').process }
+
+ context "when 'include' keyword is defined as string" do
+ context 'when the string is a local file' do
+ let(:values) do
+ {
+ include: '/lib/gitlab/ci/templates/non-existent-file.yml',
+ image: 'ruby:2.2'
+ }
+ end
+
+ it 'returns an array' do
+ expect(subject).to be_an(Array)
+ end
+
+ it 'returns File instances' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Local)
+ end
+ end
+
+ context 'when the string is a remote file' do
+ let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:values) do
+ {
+ include: remote_url,
+ image: 'ruby:2.2'
+ }
+ end
+
+ before do
+ WebMock.stub_request(:get, remote_url).to_return(body: file_content)
+ end
+
+ it 'returns an array' do
+ expect(subject).to be_an(Array)
+ end
+
+ it 'returns File instances' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Remote)
+ end
+ end
+ end
+
+ context "when 'include' is defined as an array" do
+ let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:values) do
+ {
+ include:
+ [
+ remote_url,
+ '/lib/gitlab/ci/templates/template.yml'
+ ],
+ image: 'ruby:2.2'
+ }
+ end
+
+ before do
+ WebMock.stub_request(:get, remote_url).to_return(body: file_content)
+ end
+
+ it 'returns an array' do
+ expect(subject).to be_an(Array)
+ end
+
+ it 'returns Files instances' do
+ expect(subject).to all(respond_to(:valid?))
+ expect(subject).to all(respond_to(:content))
+ end
+ end
+
+ context "when 'include' is not defined" do
+ let(:values) do
+ {
+ image: 'ruby:2.2'
+ }
+ end
+
+ it 'returns an empty array' do
+ expect(subject).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/external/processor_spec.rb b/spec/lib/gitlab/ci/external/processor_spec.rb
new file mode 100644
index 00000000000..3c7394f53d2
--- /dev/null
+++ b/spec/lib/gitlab/ci/external/processor_spec.rb
@@ -0,0 +1,182 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::External::Processor do
+ let(:project) { create(:project, :repository) }
+ let(:processor) { described_class.new(values, project, '12345') }
+
+ describe "#perform" do
+ context 'when no external files defined' do
+ let(:values) { { image: 'ruby:2.2' } }
+
+ it 'should return the same values' do
+ expect(processor.perform).to eq(values)
+ end
+ end
+
+ context 'when an invalid local file is defined' do
+ let(:values) { { include: '/lib/gitlab/ci/templates/non-existent-file.yml', image: 'ruby:2.2' } }
+
+ it 'should raise an error' do
+ expect { processor.perform }.to raise_error(
+ described_class::FileError,
+ "Local file '/lib/gitlab/ci/templates/non-existent-file.yml' is not valid."
+ )
+ end
+ end
+
+ context 'when an invalid remote file is defined' do
+ let(:remote_file) { 'http://doesntexist.com/.gitlab-ci-1.yml' }
+ let(:values) { { include: remote_file, image: 'ruby:2.2' } }
+
+ before do
+ WebMock.stub_request(:get, remote_file).to_raise(SocketError.new('Some HTTP error'))
+ end
+
+ it 'should raise an error' do
+ expect { processor.perform }.to raise_error(
+ described_class::FileError,
+ "Remote file '#{remote_file}' is not valid."
+ )
+ end
+ end
+
+ context 'with a valid remote external file is defined' do
+ let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:values) { { include: remote_file, image: 'ruby:2.2' } }
+ let(:external_file_content) do
+ <<-HEREDOC
+ before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+
+ rspec:
+ script:
+ - bundle exec rspec
+
+ rubocop:
+ script:
+ - bundle exec rubocop
+ HEREDOC
+ end
+
+ before do
+ WebMock.stub_request(:get, remote_file).to_return(body: external_file_content)
+ end
+
+ it 'should append the file to the values' do
+ output = processor.perform
+ expect(output.keys).to match_array([:image, :before_script, :rspec, :rubocop])
+ end
+
+ it "should remove the 'include' keyword" do
+ expect(processor.perform[:include]).to be_nil
+ end
+ end
+
+ context 'with a valid local external file is defined' do
+ let(:values) { { include: '/lib/gitlab/ci/templates/template.yml', image: 'ruby:2.2' } }
+ let(:local_file_content) do
+ <<-HEREDOC
+ before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+ HEREDOC
+ end
+
+ before do
+ allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ end
+
+ it 'should append the file to the values' do
+ output = processor.perform
+ expect(output.keys).to match_array([:image, :before_script])
+ end
+
+ it "should remove the 'include' keyword" do
+ expect(processor.perform[:include]).to be_nil
+ end
+ end
+
+ context 'with multiple external files are defined' do
+ let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:external_files) do
+ [
+ '/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml',
+ remote_file
+ ]
+ end
+ let(:values) do
+ {
+ include: external_files,
+ image: 'ruby:2.2'
+ }
+ end
+
+ let(:remote_file_content) do
+ <<-HEREDOC
+ stages:
+ - build
+ - review
+ - cleanup
+ HEREDOC
+ end
+
+ before do
+ local_file_content = File.read(Rails.root.join('spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml'))
+ allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
+ end
+
+ it 'should append the files to the values' do
+ expect(processor.perform.keys).to match_array([:image, :stages, :before_script, :rspec])
+ end
+
+ it "should remove the 'include' keyword" do
+ expect(processor.perform[:include]).to be_nil
+ end
+ end
+
+ context 'when external files are defined but not valid' do
+ let(:values) { { include: '/lib/gitlab/ci/templates/template.yml', image: 'ruby:2.2' } }
+
+ let(:local_file_content) { 'invalid content file ////' }
+
+ before do
+ allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ end
+
+ it 'should raise an error' do
+ expect { processor.perform }.to raise_error(Gitlab::Ci::Config::Loader::FormatError)
+ end
+ end
+
+ context "when both external files and values defined the same key" do
+ let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:values) do
+ {
+ include: remote_file,
+ image: 'ruby:2.2'
+ }
+ end
+
+ let(:remote_file_content) do
+ <<~HEREDOC
+ image: php:5-fpm-alpine
+ HEREDOC
+ end
+
+ it 'should take precedence' do
+ WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
+ expect(processor.perform[:image]).to eq('ruby:2.2')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
index f7ec86f5385..a49402c7398 100644
--- a/spec/lib/gitlab/ci/parsers/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
@@ -1,6 +1,6 @@
-require 'spec_helper'
+require 'fast_spec_helper'
-describe Gitlab::Ci::Parsers::Junit do
+describe Gitlab::Ci::Parsers::Test::Junit do
describe '#parse!' do
subject { described_class.new.parse!(junit, test_suite) }
@@ -8,21 +8,35 @@ describe Gitlab::Ci::Parsers::Junit do
let(:test_cases) { flattened_test_cases(test_suite) }
context 'when data is JUnit style XML' do
- context 'when there are no test cases' do
+ context 'when there are no <testcases> in <testsuite>' do
let(:junit) do
<<-EOF.strip_heredoc
<testsuite></testsuite>
EOF
end
- it 'raises an error and does not add any test cases' do
- expect { subject }.to raise_error(described_class::JunitParserError)
+ it 'ignores the case' do
+ expect { subject }.not_to raise_error
+
+ expect(test_cases.count).to eq(0)
+ end
+ end
+
+ context 'when there are no <testcases> in <testsuites>' do
+ let(:junit) do
+ <<-EOF.strip_heredoc
+ <testsuites><testsuite /></testsuites>
+ EOF
+ end
+
+ it 'ignores the case' do
+ expect { subject }.not_to raise_error
expect(test_cases.count).to eq(0)
end
end
- context 'when there is a test case' do
+ context 'when there is only one <testcase> in <testsuite>' do
let(:junit) do
<<-EOF.strip_heredoc
<testsuite>
@@ -40,6 +54,46 @@ describe Gitlab::Ci::Parsers::Junit do
end
end
+ context 'when there is only one <testsuite> in <testsuites>' do
+ let(:junit) do
+ <<-EOF.strip_heredoc
+ <testsuites>
+ <testsuite>
+ <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase>
+ </testsuite>
+ </testsuites>
+ EOF
+ end
+
+ it 'parses XML and adds a test case to a suite' do
+ expect { subject }.not_to raise_error
+
+ expect(test_cases[0].classname).to eq('Calculator')
+ expect(test_cases[0].name).to eq('sumTest1')
+ expect(test_cases[0].execution_time).to eq(0.01)
+ end
+ end
+
+ context 'PHPUnit' do
+ let(:junit) do
+ <<-EOF.strip_heredoc
+ <testsuites>
+ <testsuite name="Project Test Suite" tests="1" assertions="1" failures="0" errors="0" time="1.376748">
+ <testsuite name="XXX\\FrontEnd\\WebBundle\\Tests\\Controller\\LogControllerTest" file="/Users/mcfedr/projects/xxx/server/tests/XXX/FrontEnd/WebBundle/Tests/Controller/LogControllerTest.php" tests="1" assertions="1" failures="0" errors="0" time="1.376748">
+ <testcase name="testIndexAction" class="XXX\\FrontEnd\\WebBundle\\Tests\\Controller\\LogControllerTest" file="/Users/mcfedr/projects/xxx/server/tests/XXX/FrontEnd/WebBundle/Tests/Controller/LogControllerTest.php" line="9" assertions="1" time="1.376748"/>
+ </testsuite>
+ </testsuite>
+ </testsuites>
+ EOF
+ end
+
+ it 'parses XML and adds a test case to a suite' do
+ expect { subject }.not_to raise_error
+
+ expect(test_cases.count).to eq(1)
+ end
+ end
+
context 'when there are two test cases' do
let(:junit) do
<<-EOF.strip_heredoc
diff --git a/spec/lib/gitlab/ci/parsers_spec.rb b/spec/lib/gitlab/ci/parsers/test_spec.rb
index 2fa83c4abae..0b85b432677 100644
--- a/spec/lib/gitlab/ci/parsers_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Ci::Parsers do
+describe Gitlab::Ci::Parsers::Test do
describe '.fabricate!' do
subject { described_class.fabricate!(file_type) }
@@ -16,7 +16,7 @@ describe Gitlab::Ci::Parsers do
let(:file_type) { 'undefined' }
it 'raises an error' do
- expect { subject }.to raise_error(NameError)
+ expect { subject }.to raise_error(Gitlab::Ci::Parsers::Test::ParserNotFoundError)
end
end
end
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 8b92088902b..aa53ecd5967 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -319,4 +319,53 @@ describe Gitlab::Ci::Status::Build::Factory do
end
end
end
+
+ context 'when build is a delayed action' do
+ let(:build) { create(:ci_build, :scheduled) }
+
+ it 'matches correct core status' do
+ expect(factory.core_status).to be_a Gitlab::Ci::Status::Scheduled
+ end
+
+ it 'matches correct extended statuses' do
+ expect(factory.extended_statuses)
+ .to eq [Gitlab::Ci::Status::Build::Scheduled,
+ Gitlab::Ci::Status::Build::Unschedule,
+ Gitlab::Ci::Status::Build::Action]
+ end
+
+ it 'fabricates action detailed status' do
+ expect(status).to be_a Gitlab::Ci::Status::Build::Action
+ end
+
+ it 'fabricates status with correct details' do
+ expect(status.text).to eq 'scheduled'
+ expect(status.group).to eq 'scheduled'
+ expect(status.icon).to eq 'status_scheduled'
+ expect(status.favicon).to eq 'favicon_status_scheduled'
+ expect(status.illustration).to include(:image, :size, :title, :content)
+ expect(status.label).to include 'unschedule action'
+ expect(status).to have_details
+ expect(status.action_path).to include 'unschedule'
+ end
+
+ context 'when user has ability to play action' do
+ it 'fabricates status that has action' do
+ expect(status).to have_action
+ end
+ end
+
+ context 'when user does not have ability to play action' do
+ before do
+ allow(build.project).to receive(:empty_repo?).and_return(false)
+
+ create(:protected_branch, :no_one_can_push,
+ name: build.ref, project: build.project)
+ end
+
+ it 'fabricates status that has no action' do
+ expect(status).not_to have_action
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
new file mode 100644
index 00000000000..3098a17c50d
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Scheduled do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :stubbed_repository) }
+ let(:build) { create(:ci_build, :scheduled, project: project) }
+ let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+ subject { described_class.new(status) }
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title) }
+ end
+
+ describe '#status_tooltip' do
+ context 'when scheduled_at is not expired' do
+ let(:build) { create(:ci_build, scheduled_at: 1.minute.since, project: project) }
+
+ it 'shows execute_in of the scheduled job' do
+ Timecop.freeze do
+ expect(subject.status_tooltip).to include('00:01:00')
+ end
+ end
+ end
+
+ context 'when scheduled_at is expired' do
+ let(:build) { create(:ci_build, :expired_scheduled, project: project) }
+
+ it 'shows 00:00:00' do
+ Timecop.freeze do
+ expect(subject.status_tooltip).to include('00:00:00')
+ end
+ end
+ end
+ end
+
+ describe '.matches?' do
+ subject { described_class.matches?(build, user) }
+
+ context 'when build is scheduled and scheduled_at is present' do
+ let(:build) { create(:ci_build, :expired_scheduled, project: project) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when build is scheduled' do
+ let(:build) { create(:ci_build, status: :scheduled, project: project) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when scheduled_at is present' do
+ let(:build) { create(:ci_build, scheduled_at: 1.minute.since, project: project) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/unschedule_spec.rb b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
new file mode 100644
index 00000000000..ed046d66ca5
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Unschedule do
+ let(:status) { double('core status') }
+ let(:user) { double('user') }
+
+ subject do
+ described_class.new(status)
+ end
+
+ describe '#label' do
+ it { expect(subject.label).to eq 'unschedule action' }
+ end
+
+ describe 'action details' do
+ let(:user) { create(:user) }
+ let(:build) { create(:ci_build) }
+ let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+ describe '#has_action?' do
+ context 'when user is allowed to update build' do
+ before do
+ stub_not_protect_default_branch
+
+ build.project.add_developer(user)
+ end
+
+ it { is_expected.to have_action }
+ end
+
+ context 'when user is not allowed to update build' do
+ it { is_expected.not_to have_action }
+ end
+ end
+
+ describe '#action_path' do
+ it { expect(subject.action_path).to include "#{build.id}/unschedule" }
+ end
+
+ describe '#action_icon' do
+ it { expect(subject.action_icon).to eq 'time-out' }
+ end
+
+ describe '#action_title' do
+ it { expect(subject.action_title).to eq 'Unschedule' }
+ end
+
+ describe '#action_button_title' do
+ it { expect(subject.action_button_title).to eq 'Unschedule job' }
+ end
+ end
+
+ describe '.matches?' do
+ subject { described_class.matches?(build, user) }
+
+ context 'when build is scheduled' do
+ context 'when build unschedules an delayed job' do
+ let(:build) { create(:ci_build, :scheduled) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build unschedules an normal job' do
+ let(:build) { create(:ci_build) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+ end
+
+ describe '#status_tooltip' do
+ it 'does not override status status_tooltip' do
+ expect(status).to receive(:status_tooltip)
+
+ subject.status_tooltip
+ end
+ end
+
+ describe '#badge_tooltip' do
+ let(:user) { create(:user) }
+ let(:build) { create(:ci_build, :playable) }
+ let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+ it 'does not override status badge_tooltip' do
+ expect(status).to receive(:badge_tooltip)
+
+ subject.badge_tooltip
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index defb3fdc0df..694d4ce160a 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -11,8 +11,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
end
context 'when pipeline has a core status' do
- (HasStatus::AVAILABLE_STATUSES - [HasStatus::BLOCKED_STATUS])
- .each do |simple_status|
+ HasStatus::AVAILABLE_STATUSES.each do |simple_status|
context "when core status is #{simple_status}" do
let(:pipeline) { create(:ci_pipeline, status: simple_status) }
@@ -24,12 +23,24 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
expect(factory.core_status).to be_a expected_status
end
- it 'does not match extended statuses' do
- expect(factory.extended_statuses).to be_empty
- end
-
- it "fabricates a core status #{simple_status}" do
- expect(status).to be_a expected_status
+ if simple_status == 'manual'
+ it 'matches a correct extended statuses' do
+ expect(factory.extended_statuses)
+ .to eq [Gitlab::Ci::Status::Pipeline::Blocked]
+ end
+ elsif simple_status == 'scheduled'
+ it 'matches a correct extended statuses' do
+ expect(factory.extended_statuses)
+ .to eq [Gitlab::Ci::Status::Pipeline::Scheduled]
+ end
+ else
+ it 'does not match extended statuses' do
+ expect(factory.extended_statuses).to be_empty
+ end
+
+ it "fabricates a core status #{simple_status}" do
+ expect(status).to be_a expected_status
+ end
end
it 'extends core status with common pipeline methods' do
@@ -40,27 +51,6 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
end
end
end
-
- context "when core status is manual" do
- let(:pipeline) { create(:ci_pipeline, status: :manual) }
-
- it "matches manual core status" do
- expect(factory.core_status)
- .to be_a Gitlab::Ci::Status::Manual
- end
-
- it 'matches a correct extended statuses' do
- expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Pipeline::Blocked]
- end
-
- it 'extends core status with common pipeline methods' do
- expect(status).to have_details
- expect(status).not_to have_action
- expect(status.details_path)
- .to include "pipelines/#{pipeline.id}"
- end
- end
end
context 'when pipeline has warnings' do
diff --git a/spec/lib/gitlab/ci/status/pipeline/scheduled_spec.rb b/spec/lib/gitlab/ci/status/pipeline/scheduled_spec.rb
new file mode 100644
index 00000000000..29afa08b56b
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/pipeline/scheduled_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Pipeline::Scheduled do
+ let(:pipeline) { double('pipeline') }
+
+ subject do
+ described_class.new(pipeline)
+ end
+
+ describe '#text' do
+ it 'overrides status text' do
+ expect(subject.text).to eq 'scheduled'
+ end
+ end
+
+ describe '#label' do
+ it 'overrides status label' do
+ expect(subject.label).to eq 'waiting for delayed job'
+ end
+ end
+
+ describe '.matches?' do
+ let(:user) { double('user') }
+ subject { described_class.matches?(pipeline, user) }
+
+ context 'when pipeline is scheduled' do
+ let(:pipeline) { create(:ci_pipeline, :scheduled) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when pipeline is not scheduled' do
+ let(:pipeline) { create(:ci_pipeline, :success) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/scheduled_spec.rb b/spec/lib/gitlab/ci/status/scheduled_spec.rb
new file mode 100644
index 00000000000..c35a6f43d5d
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/scheduled_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Scheduled do
+ subject do
+ described_class.new(double('subject'), double('user'))
+ end
+
+ describe '#text' do
+ it { expect(subject.text).to eq 'scheduled' }
+ end
+
+ describe '#label' do
+ it { expect(subject.label).to eq 'scheduled' }
+ end
+
+ describe '#icon' do
+ it { expect(subject.icon).to eq 'status_scheduled' }
+ end
+
+ describe '#favicon' do
+ it { expect(subject.favicon).to eq 'favicon_status_scheduled' }
+ end
+
+ describe '#group' do
+ it { expect(subject.group).to eq 'scheduled' }
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb
new file mode 100644
index 00000000000..0dd74399a47
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/templates_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe "CI YML Templates" do
+ Gitlab::Template::GitlabCiYmlTemplate.all.each do |template|
+ it "#{template.name} should be valid" do
+ expect { Gitlab::Ci::YamlProcessor.new(template.content) }.not_to raise_error
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index e73cdc54a15..85b23edce9f 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -121,6 +121,21 @@ module Gitlab
end
end
end
+
+ describe 'delayed job entry' do
+ context 'when delayed is defined' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rollout 10%',
+ when: 'delayed',
+ start_in: '1 day' })
+ end
+
+ it 'has the attributes' do
+ expect(subject[:when]).to eq 'delayed'
+ expect(subject[:options][:start_in]).to eq '1 day'
+ end
+ end
+ end
end
describe '#stages_attributes' do
@@ -562,6 +577,58 @@ module Gitlab
end
end
+ context 'when using `extends`' do
+ let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) }
+
+ subject { config_processor.builds.first }
+
+ context 'when using simple `extends`' do
+ let(:config) do
+ <<~YAML
+ .template:
+ script: test
+
+ rspec:
+ extends: .template
+ image: ruby:alpine
+ YAML
+ end
+
+ it 'correctly extends rspec job' do
+ expect(config_processor.builds).to be_one
+ expect(subject.dig(:commands)).to eq 'test'
+ expect(subject.dig(:options, :image, :name)).to eq 'ruby:alpine'
+ end
+ end
+
+ context 'when using recursive `extends`' do
+ let(:config) do
+ <<~YAML
+ rspec:
+ extends: .test
+ script: rspec
+ when: always
+
+ .template:
+ before_script:
+ - bundle install
+
+ .test:
+ extends: .template
+ script: test
+ image: image:test
+ YAML
+ end
+
+ it 'correctly extends rspec job' do
+ expect(config_processor.builds).to be_one
+ expect(subject.dig(:commands)).to eq "bundle install\nrspec"
+ expect(subject.dig(:options, :image, :name)).to eq 'image:test'
+ expect(subject.dig(:when)).to eq 'always'
+ end
+ end
+ end
+
describe "When" do
%w(on_success on_failure always).each do |when_state|
it "returns #{when_state} when defined" do
@@ -1208,7 +1275,7 @@ module Gitlab
config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do
Gitlab::Ci::YamlProcessor.new(config)
- end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure, always or manual")
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure, always, manual or delayed")
end
it "returns errors if job artifacts:name is not an a string" do
@@ -1302,24 +1369,28 @@ module Gitlab
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
+ it 'returns errors if pipeline variables expression policy 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
- templates = Dir.glob("#{Rails.root.join('vendor/gitlab-ci-yml')}/**/*.gitlab-ci.yml")
+ it 'returns errors if pipeline changes policy is invalid' do
+ config = YAML.dump({ rspec: { script: 'test', only: { changes: [1] } } })
- templates.each do |file|
- it "does not return errors for #{file}" do
- file = File.read(file)
+ expect { Gitlab::Ci::YamlProcessor.new(config) }
+ .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'jobs:rspec:only changes should be an array of strings')
+ end
- expect { Gitlab::Ci::YamlProcessor.new(file) }.not_to raise_error
- end
+ it 'returns errors if extended hash configuration is invalid' do
+ config = YAML.dump({ rspec: { extends: 'something', script: 'test' } })
+
+ expect { Gitlab::Ci::YamlProcessor.new(config) }
+ .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'rspec: unknown key in `extends`')
end
end
diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
index 11e605eece6..bf130b8fabd 100644
--- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
@@ -132,7 +132,6 @@ describe Gitlab::Cleanup::ProjectUploads do
let!(:path) { File.join(FileUploader.root, orphaned.model.full_path, orphaned.path) }
before do
- stub_feature_flags(import_export_object_storage: true)
stub_uploads_object_storage(FileUploader)
FileUtils.mkdir_p(File.dirname(path))
@@ -156,7 +155,6 @@ describe Gitlab::Cleanup::ProjectUploads do
let!(:new_path) { File.join(FileUploader.root, '-', 'project-lost-found', 'wrong', orphaned.path) }
before do
- stub_feature_flags(import_export_object_storage: true)
stub_uploads_object_storage(FileUploader)
FileUtils.mkdir_p(File.dirname(path))
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 1f35d1e4880..44568f2a653 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -338,6 +338,13 @@ describe Gitlab::ClosingIssueExtractor do
end
end
+ context "with an invalid keyword such as suffix insted of fix" do
+ it do
+ message = "suffix #{reference}"
+ expect(subject.closed_by_message(message)).to eq([])
+ end
+ end
+
context 'with multiple references' do
let(:other_issue) { create(:issue, project: project) }
let(:third_issue) { create(:issue, project: project) }
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 9095ffbfd52..1bd077ddbdf 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -1,9 +1,11 @@
require 'spec_helper'
describe Gitlab::Conflict::File do
+ include GitHelpers
+
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
- let(:rugged) { Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.rugged } }
+ let(:rugged) { rugged_repo(repository) }
let(:their_commit) { rugged.branches['conflict-start'].target }
let(:our_commit) { rugged.branches['conflict-resolvable'].target }
let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) }
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 2c63f3b0455..6d29044ffd5 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -62,13 +62,16 @@ describe Gitlab::ContributionsCalendar do
expect(calendar.activity_dates).to eq(last_week => 2, today => 1)
end
- it "only shows private events to authorized users" do
- create_event(private_project, today)
- create_event(feature_project, today)
+ context "when the user has opted-in for private contributions" do
+ it "shows private and public events to all users" do
+ user.update_column(:include_private_contributions, true)
+ create_event(private_project, today)
+ create_event(public_project, today)
- expect(calendar.activity_dates[today]).to eq(0)
- expect(calendar(user).activity_dates[today]).to eq(0)
- expect(calendar(contributor).activity_dates[today]).to eq(2)
+ expect(calendar.activity_dates[today]).to eq(1)
+ expect(calendar(user).activity_dates[today]).to eq(1)
+ expect(calendar(contributor).activity_dates[today]).to eq(2)
+ end
end
it "counts the diff notes on merge request" do
@@ -128,7 +131,7 @@ describe Gitlab::ContributionsCalendar do
e3 = create_event(feature_project, today)
create_event(public_project, last_week)
- expect(calendar.events_by_date(today)).to contain_exactly(e1)
+ expect(calendar.events_by_date(today)).to contain_exactly(e1, e3)
expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3)
end
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 9ca960502c8..98f1696badb 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -6,10 +6,10 @@ describe Gitlab::DataBuilder::Pipeline do
let(:pipeline) do
create(:ci_pipeline,
- project: project,
- status: 'success',
- sha: project.commit.sha,
- ref: project.default_branch)
+ project: project,
+ status: 'success',
+ sha: project.commit.sha,
+ ref: project.default_branch)
end
let!(:build) { create(:ci_build, pipeline: pipeline) }
@@ -20,18 +20,35 @@ describe Gitlab::DataBuilder::Pipeline do
let(:build_data) { data[:builds].first }
let(:project_data) { data[:project] }
- it { expect(attributes).to be_a(Hash) }
- it { expect(attributes[:ref]).to eq(pipeline.ref) }
- it { expect(attributes[:sha]).to eq(pipeline.sha) }
- it { expect(attributes[:tag]).to eq(pipeline.tag) }
- it { expect(attributes[:id]).to eq(pipeline.id) }
- it { expect(attributes[:status]).to eq(pipeline.status) }
- it { expect(attributes[:detailed_status]).to eq('passed') }
+ it 'has correct attributes' do
+ expect(attributes).to be_a(Hash)
+ expect(attributes[:ref]).to eq(pipeline.ref)
+ expect(attributes[:sha]).to eq(pipeline.sha)
+ expect(attributes[:tag]).to eq(pipeline.tag)
+ expect(attributes[:id]).to eq(pipeline.id)
+ expect(attributes[:status]).to eq(pipeline.status)
+ expect(attributes[:detailed_status]).to eq('passed')
+ expect(build_data).to be_a(Hash)
+ expect(build_data[:id]).to eq(build.id)
+ expect(build_data[:status]).to eq(build.status)
+ expect(project_data).to eq(project.hook_attrs(backward: false))
+ end
- it { expect(build_data).to be_a(Hash) }
- it { expect(build_data[:id]).to eq(build.id) }
- it { expect(build_data[:status]).to eq(build.status) }
+ context 'pipeline without variables' do
+ it 'has empty variables hash' do
+ expect(attributes[:variables]).to be_a(Array)
+ expect(attributes[:variables]).to be_empty()
+ end
+ end
- it { expect(project_data).to eq(project.hook_attrs(backward: false)) }
+ context 'pipeline with variables' do
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:data) { described_class.build(pipeline) }
+ let(:attributes) { data[:object_attributes] }
+ let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1') }
+
+ it { expect(attributes[:variables]).to be_a(Array) }
+ it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) }
+ end
end
end
diff --git a/spec/lib/gitlab/database/subquery_spec.rb b/spec/lib/gitlab/database/subquery_spec.rb
new file mode 100644
index 00000000000..70380e02f16
--- /dev/null
+++ b/spec/lib/gitlab/database/subquery_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Subquery do
+ describe '.self_join' do
+ set(:project) { create(:project) }
+
+ it 'allows you to delete_all rows with WHERE and LIMIT' do
+ events = create_list(:event, 8, project: project)
+
+ expect do
+ described_class.self_join(Event.where('id < ?', events[5]).recent.limit(2)).delete_all
+ end.to change { Event.count }.by(-2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/file_collection/commit_spec.rb b/spec/lib/gitlab/diff/file_collection/commit_spec.rb
new file mode 100644
index 00000000000..6d1b66deb6a
--- /dev/null
+++ b/spec/lib/gitlab/diff/file_collection/commit_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::FileCollection::Commit do
+ let(:project) { create(:project, :repository) }
+
+ it_behaves_like 'diff statistics' do
+ let(:collection_default_args) do
+ { diff_options: {} }
+ end
+ let(:diffable) { project.commit }
+ let(:stub_path) { 'bar/branch-test.txt' }
+ end
+end
diff --git a/spec/lib/gitlab/diff/file_collection/compare_spec.rb b/spec/lib/gitlab/diff/file_collection/compare_spec.rb
new file mode 100644
index 00000000000..f330f299ac1
--- /dev/null
+++ b/spec/lib/gitlab/diff/file_collection/compare_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::FileCollection::Compare do
+ include RepoHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:commit) { project.commit }
+ let(:start_commit) { sample_image_commit }
+ let(:head_commit) { sample_commit }
+ let(:raw_compare) do
+ Gitlab::Git::Compare.new(project.repository.raw_repository,
+ start_commit.id,
+ head_commit.id)
+ end
+
+ it_behaves_like 'diff statistics' do
+ let(:collection_default_args) do
+ {
+ project: diffable.project,
+ diff_options: {},
+ diff_refs: diffable.diff_refs
+ }
+ end
+ let(:diffable) { Compare.new(raw_compare, project) }
+ let(:stub_path) { '.gitignore' }
+ end
+end
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index 79287021981..4578da70bfc 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -29,6 +29,14 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do
expect(mr_diff.cache_key).not_to eq(key)
end
+ it_behaves_like 'diff statistics' do
+ let(:collection_default_args) do
+ { diff_options: {} }
+ end
+ let(:diffable) { merge_request.merge_request_diff }
+ let(:stub_path) { '.gitignore' }
+ end
+
shared_examples 'initializes a DiffCollection' do
it 'returns a valid instance of a DiffCollection' do
expect(diff_files).to be_a(Gitlab::Git::DiffCollection)
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index ebeb05d6e02..2f51642b58e 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -186,6 +186,70 @@ describe Gitlab::Diff::File do
end
end
+ context 'diff file stats' do
+ let(:diff_file) do
+ described_class.new(diff,
+ diff_refs: commit.diff_refs,
+ repository: project.repository,
+ stats: stats)
+ end
+
+ let(:raw_diff) do
+ <<~EOS
+ --- a/files/ruby/popen.rb
+ +++ b/files/ruby/popen.rb
+ @@ -6,12 +6,18 @@ module Popen
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ - raise "System commands must be given as an array of strings"
+ + raise RuntimeError, "System commands must be given as an array of strings"
+ + # foobar
+ end
+ EOS
+ end
+
+ describe '#added_lines' do
+ context 'when stats argument given' do
+ let(:stats) { double(Gitaly::DiffStats, additions: 10, deletions: 15) }
+
+ it 'returns added lines from stats' do
+ expect(diff_file.added_lines).to eq(stats.additions)
+ end
+ end
+
+ context 'when stats argument not given' do
+ let(:stats) { nil }
+
+ it 'returns added lines by parsing raw diff' do
+ allow(diff_file).to receive(:raw_diff) { raw_diff }
+
+ expect(diff_file.added_lines).to eq(2)
+ end
+ end
+ end
+
+ describe '#removed_lines' do
+ context 'when stats argument given' do
+ let(:stats) { double(Gitaly::DiffStats, additions: 10, deletions: 15) }
+
+ it 'returns removed lines from stats' do
+ expect(diff_file.removed_lines).to eq(stats.deletions)
+ end
+ end
+
+ context 'when stats argument not given' do
+ let(:stats) { nil }
+
+ it 'returns removed lines by parsing raw diff' do
+ allow(diff_file).to receive(:raw_diff) { raw_diff }
+
+ expect(diff_file.removed_lines).to eq(1)
+ end
+ end
+ end
+ end
+
describe '#simple_viewer' do
context 'when the file is not diffable' do
before do
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
new file mode 100644
index 00000000000..bfcfed4231f
--- /dev/null
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::HighlightCache do
+ let(:merge_request) { create(:merge_request_with_diffs) }
+
+ subject(:cache) { described_class.new(merge_request.diffs, backend: backend) }
+
+ describe '#decorate' do
+ let(:backend) { double('backend').as_null_object }
+
+ # Manually creates a Diff::File object to avoid triggering the cache on
+ # the FileCollection::MergeRequestDiff
+ let(:diff_file) do
+ diffs = merge_request.diffs
+ raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['CHANGELOG'])).first
+ Gitlab::Diff::File.new(raw_diff,
+ repository: diffs.project.repository,
+ diff_refs: diffs.diff_refs,
+ fallback_diff_refs: diffs.fallback_diff_refs)
+ end
+
+ it 'does not calculate highlighting when reading from cache' do
+ cache.write_if_empty
+ cache.decorate(diff_file)
+
+ expect_any_instance_of(Gitlab::Diff::Highlight).not_to receive(:highlight)
+
+ diff_file.highlighted_diff_lines
+ end
+
+ it 'assigns highlighted diff lines to the DiffFile' do
+ cache.write_if_empty
+ cache.decorate(diff_file)
+
+ expect(diff_file.highlighted_diff_lines.size).to be > 5
+ end
+
+ it 'submits a single reading from the cache' do
+ cache.decorate(diff_file)
+ cache.decorate(diff_file)
+
+ expect(backend).to have_received(:read).with(cache.key).once
+ end
+ end
+
+ describe '#write_if_empty' do
+ let(:backend) { double('backend', read: {}).as_null_object }
+
+ it 'submits a single writing to the cache' do
+ cache.write_if_empty
+ cache.write_if_empty
+
+ expect(backend).to have_received(:write).with(cache.key,
+ hash_including('CHANGELOG-false-false-false'),
+ expires_in: 1.week).once
+ end
+ end
+
+ describe '#clear' do
+ let(:backend) { double('backend').as_null_object }
+
+ it 'clears cache' do
+ cache.clear
+
+ expect(backend).to have_received(:delete).with(cache.key)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 3c8cf9c56cc..5d0a603d11d 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -8,6 +8,20 @@ describe Gitlab::Diff::Highlight do
let(:diff) { commit.raw_diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
+ shared_examples 'without inline diffs' do
+ let(:code) { '<h2 onmouseover="alert(2)">Test</h2>' }
+
+ before do
+ allow(Gitlab::Diff::InlineDiff).to receive(:for_lines).and_return([])
+ allow_any_instance_of(Gitlab::Diff::Line).to receive(:text).and_return(code)
+ end
+
+ it 'returns html escaped diff text' do
+ expect(subject[1].rich_text).to eq html_escape(code)
+ expect(subject[1].rich_text).to be_html_safe
+ end
+ end
+
describe '#highlight' do
context "with a diff file" do
let(:subject) { described_class.new(diff_file, repository: project.repository).highlight }
@@ -38,6 +52,16 @@ describe Gitlab::Diff::Highlight do
expect(subject[5].rich_text).to eq(code)
end
+
+ context 'when no diff_refs' do
+ before do
+ allow(diff_file).to receive(:diff_refs).and_return(nil)
+ end
+
+ context 'when no inline diffs' do
+ it_behaves_like 'without inline diffs'
+ end
+ end
end
context "with diff lines" do
@@ -93,6 +117,10 @@ describe Gitlab::Diff::Highlight do
expect { subject }. to raise_exception(RangeError)
end
end
+
+ context 'when no inline diffs' do
+ it_behaves_like 'without inline diffs'
+ end
end
end
end
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index 677eb373d22..2d94356f386 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -5,6 +5,34 @@ describe Gitlab::Diff::Position do
let(:project) { create(:project, :repository) }
+ let(:args_for_img) do
+ {
+ old_path: "files/any.img",
+ new_path: "files/any.img",
+ base_sha: nil,
+ head_sha: nil,
+ start_sha: nil,
+ width: 100,
+ height: 100,
+ x: 1,
+ y: 100,
+ position_type: "image"
+ }
+ end
+
+ let(:args_for_text) do
+ {
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ base_sha: nil,
+ head_sha: nil,
+ start_sha: nil,
+ position_type: "text"
+ }
+ end
+
describe "position for an added text file" do
let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") }
@@ -529,53 +557,49 @@ describe Gitlab::Diff::Position do
end
end
+ describe "#as_json" do
+ shared_examples "diff position json" do
+ let(:diff_position) { described_class.new(args) }
+
+ it "returns the position as JSON" do
+ expect(diff_position.as_json).to eq(args.stringify_keys)
+ end
+ end
+
+ context "for text positon" do
+ let(:args) { args_for_text }
+
+ it_behaves_like "diff position json"
+ end
+
+ context "for image positon" do
+ let(:args) { args_for_img }
+
+ it_behaves_like "diff position json"
+ end
+ end
+
describe "#to_json" do
shared_examples "diff position json" do
+ let(:diff_position) { described_class.new(args) }
+
it "returns the position as JSON" do
- expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys)
+ expect(JSON.parse(diff_position.to_json)).to eq(args.stringify_keys)
end
it "works when nested under another hash" do
- expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys)
+ expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => args.stringify_keys)
end
end
context "for text positon" do
- let(:hash) do
- {
- old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
- old_line: nil,
- new_line: 14,
- base_sha: nil,
- head_sha: nil,
- start_sha: nil,
- position_type: "text"
- }
- end
-
- let(:diff_position) { described_class.new(hash) }
+ let(:args) { args_for_text }
it_behaves_like "diff position json"
end
context "for image positon" do
- let(:hash) do
- {
- old_path: "files/any.img",
- new_path: "files/any.img",
- base_sha: nil,
- head_sha: nil,
- start_sha: nil,
- width: 100,
- height: 100,
- x: 1,
- y: 100,
- position_type: "image"
- }
- end
-
- let(:diff_position) { described_class.new(hash) }
+ let(:args) { args_for_img }
it_behaves_like "diff position json"
end
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index 154ab4b3856..1d75e8cb5da 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler::CreateIssueHandler do
diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
index 43c6280f251..ace3104f36f 100644
--- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler::CreateMergeRequestHandler do
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index 950a7dd7d6c..b1f48c15c21 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler::CreateNoteHandler do
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
index ce160e11de2..b8660b133ec 100644
--- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler::UnsubscribeHandler do
diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index cedbfcc0d18..c651765dc0f 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler do
@@ -40,7 +42,7 @@ describe Gitlab::Email::Handler do
end
def ce_handlers
- @ce_handlers ||= Gitlab::Email::Handler::HANDLERS.reject do |handler|
+ @ce_handlers ||= Gitlab::Email::Handler.handlers.reject do |handler|
handler.name.start_with?('Gitlab::Email::Handler::EE::')
end
end
diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb
index 68abcb3520a..49a423191bb 100644
--- a/spec/lib/gitlab/favicon_spec.rb
+++ b/spec/lib/gitlab/favicon_spec.rb
@@ -58,6 +58,7 @@ RSpec.describe Gitlab::Favicon, :request_store do
favicon_status_not_found
favicon_status_pending
favicon_status_running
+ favicon_status_scheduled
favicon_status_skipped
favicon_status_success
favicon_status_warning
diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb
index 8e524f9b05a..9e351368b22 100644
--- a/spec/lib/gitlab/file_detector_spec.rb
+++ b/spec/lib/gitlab/file_detector_spec.rb
@@ -29,11 +29,15 @@ describe Gitlab::FileDetector do
end
it 'returns the type of a license file' do
- %w(LICENSE LICENCE COPYING).each do |file|
+ %w(LICENSE LICENCE COPYING UNLICENSE UNLICENCE).each do |file|
expect(described_class.type_of(file)).to eq(:license)
end
end
+ it 'returns nil for an UNCOPYING file' do
+ expect(described_class.type_of('UNCOPYING')).to be_nil
+ end
+
it 'returns the type of a version file' do
expect(described_class.type_of('VERSION')).to eq(:version)
end
diff --git a/spec/lib/gitlab/file_markdown_link_builder_spec.rb b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
new file mode 100644
index 00000000000..feb2776c5d0
--- /dev/null
+++ b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+require 'rails_helper'
+
+describe Gitlab::FileMarkdownLinkBuilder do
+ let(:custom_class) do
+ Class.new do
+ include Gitlab::FileMarkdownLinkBuilder
+ end.new
+ end
+
+ before do
+ allow(custom_class).to receive(:filename).and_return(filename)
+ end
+
+ describe 'markdown_link' do
+ let(:url) { "/uploads/#{filename}"}
+
+ before do
+ allow(custom_class).to receive(:secure_url).and_return(url)
+ end
+
+ context 'when file name has the character ]' do
+ let(:filename) { 'd]k.png' }
+
+ it 'escapes the character' do
+ expect(custom_class.markdown_link).to eq '![d\\]k](/uploads/d]k.png)'
+ end
+ end
+
+ context 'when file is an image or video' do
+ let(:filename) { 'dk.png' }
+
+ it 'returns preview markdown link' do
+ expect(custom_class.markdown_link).to eq '![dk](/uploads/dk.png)'
+ end
+ end
+
+ context 'when file is not an image or video' do
+ let(:filename) { 'dk.zip' }
+
+ it 'returns markdown link' do
+ expect(custom_class.markdown_link).to eq '[dk.zip](/uploads/dk.zip)'
+ end
+ end
+
+ context 'when file name is blank' do
+ let(:filename) { nil }
+
+ it 'returns nil' do
+ expect(custom_class.markdown_link).to eq nil
+ end
+ end
+ end
+
+ describe 'mardown_name' do
+ context 'when file is an image or video' do
+ let(:filename) { 'dk.png' }
+
+ it 'retrieves the name without the extension' do
+ expect(custom_class.markdown_name).to eq 'dk'
+ end
+ end
+
+ context 'when file is not an image or video' do
+ let(:filename) { 'dk.zip' }
+
+ it 'retrieves the name with the extesion' do
+ expect(custom_class.markdown_name).to eq 'dk.zip'
+ end
+ end
+
+ context 'when file name is blank' do
+ let(:filename) { nil }
+
+ it 'returns nil' do
+ expect(custom_class.markdown_name).to eq nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/file_type_detection_spec.rb b/spec/lib/gitlab/file_type_detection_spec.rb
new file mode 100644
index 00000000000..5e9b8988cc8
--- /dev/null
+++ b/spec/lib/gitlab/file_type_detection_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+require 'rails_helper'
+
+describe Gitlab::FileTypeDetection do
+ def upload_fixture(filename)
+ fixture_file_upload(File.join('spec', 'fixtures', filename))
+ end
+
+ describe '#image_or_video?' do
+ context 'when class is an uploader' do
+ let(:uploader) do
+ example_uploader = Class.new(CarrierWave::Uploader::Base) do
+ include Gitlab::FileTypeDetection
+
+ storage :file
+ end
+
+ example_uploader.new
+ end
+
+ it 'returns true for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).to be_image_or_video
+ end
+
+ it 'returns true for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).to be_image_or_video
+ end
+
+ it 'returns false for other extensions' do
+ uploader.store!(upload_fixture('doc_sample.txt'))
+
+ expect(uploader).not_to be_image_or_video
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_image_or_video
+ end
+ end
+
+ context 'when class is a regular class' do
+ let(:custom_class) do
+ custom_class = Class.new do
+ include Gitlab::FileTypeDetection
+ end
+
+ custom_class.new
+ end
+
+ it 'returns true for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).to be_image_or_video
+ end
+
+ it 'returns true for a video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).to be_image_or_video
+ end
+
+ it 'returns false for other extensions' do
+ allow(custom_class).to receive(:filename).and_return('doc_sample.txt')
+
+ expect(custom_class).not_to be_image_or_video
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_image_or_video
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index ea49502ae2e..b243f0dacae 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -4,6 +4,9 @@ require "spec_helper"
describe Gitlab::Git::Blob, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:rugged) do
+ Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH))
+ end
describe 'initialize' do
let(:blob) { Gitlab::Git::Blob.new(name: 'test') }
@@ -139,9 +142,7 @@ describe Gitlab::Git::Blob, :seed_helper do
it 'limits the size of a large file' do
blob_size = Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE + 1
buffer = Array.new(blob_size, 0)
- rugged_blob = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Rugged::Blob.from_buffer(repository.rugged, buffer.join(''))
- end
+ rugged_blob = Rugged::Blob.from_buffer(rugged, buffer.join(''))
blob = Gitlab::Git::Blob.raw(repository, rugged_blob)
expect(blob.size).to eq(blob_size)
@@ -156,9 +157,7 @@ describe Gitlab::Git::Blob, :seed_helper do
context 'when sha references a tree' do
it 'returns nil' do
- tree = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.rev_parse('master^{tree}')
- end
+ tree = rugged.rev_parse('master^{tree}')
blob = Gitlab::Git::Blob.raw(repository, tree.oid)
@@ -262,11 +261,7 @@ describe Gitlab::Git::Blob, :seed_helper do
end
describe '.batch_lfs_pointers' do
- let(:tree_object) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.rev_parse('master^{tree}')
- end
- end
+ let(:tree_object) { rugged.rev_parse('master^{tree}') }
let(:non_lfs_blob) do
Gitlab::Git::Blob.find(
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 79ccbb79966..0df282d0ae3 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -3,9 +3,7 @@ require "spec_helper"
describe Gitlab::Git::Branch, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
let(:rugged) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged
- end
+ Rugged::Repository.new(File.join(TestEnv.repos_path, repository.relative_path))
end
subject { repository.branches }
@@ -74,9 +72,7 @@ describe Gitlab::Git::Branch, :seed_helper do
Gitlab::Git.committer_hash(email: user.email, name: user.name)
end
let(:params) do
- parents = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- [repository.rugged.head.target]
- end
+ parents = [rugged.head.target]
tree = parents.first.tree
{
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 2718a3c5e49..9ef27081f98 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -1,19 +1,17 @@
require "spec_helper"
describe Gitlab::Git::Commit, :seed_helper do
+ include GitHelpers
+
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
- let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) }
- let(:rugged_commit) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.lookup(SeedRepo::Commit::ID)
- end
+ let(:rugged_repo) do
+ Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH))
end
+ let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) }
+ let(:rugged_commit) { rugged_repo.lookup(SeedRepo::Commit::ID) }
+
describe "Commit info" do
before do
- repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- end
-
@committer = {
email: 'mike@smith.com',
name: "Mike Smith",
@@ -26,12 +24,12 @@ describe Gitlab::Git::Commit, :seed_helper do
time: Time.now
}
- @parents = [repo.head.target]
+ @parents = [rugged_repo.head.target]
@gitlab_parents = @parents.map { |c| described_class.find(repository, c.oid) }
@tree = @parents.first.tree
sha = Rugged::Commit.create(
- repo,
+ rugged_repo,
author: @author,
committer: @committer,
tree: @tree,
@@ -40,7 +38,7 @@ describe Gitlab::Git::Commit, :seed_helper do
update_ref: "HEAD"
)
- @raw_commit = repo.lookup(sha)
+ @raw_commit = rugged_repo.lookup(sha)
@commit = described_class.find(repository, sha)
end
@@ -61,10 +59,7 @@ describe Gitlab::Git::Commit, :seed_helper do
after do
# Erase the new commit so other tests get the original repo
- repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- end
- repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
+ rugged_repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
end
end
@@ -120,9 +115,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '.find' do
it "should return first head commit if without params" do
expect(described_class.last(repository).id).to eq(
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.head.target.oid
- end
+ rugged_repo.head.target.oid
)
end
diff --git a/spec/lib/gitlab/git/committer_with_hooks_spec.rb b/spec/lib/gitlab/git/committer_with_hooks_spec.rb
deleted file mode 100644
index c7626058acd..00000000000
--- a/spec/lib/gitlab/git/committer_with_hooks_spec.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::CommitterWithHooks, :seed_helper do
- # TODO https://gitlab.com/gitlab-org/gitaly/issues/1234
- skip 'needs to be moved to gitaly-ruby test suite' do
- shared_examples 'calling wiki hooks' do
- let(:project) { create(:project) }
- let(:user) { project.owner }
- let(:project_wiki) { ProjectWiki.new(project, user) }
- let(:wiki) { project_wiki.wiki }
- let(:options) do
- {
- id: user.id,
- username: user.username,
- name: user.name,
- email: user.email,
- message: 'commit message'
- }
- end
-
- subject { described_class.new(wiki, options) }
-
- before do
- project_wiki.create_page('home', 'test content')
- end
-
- shared_examples 'failing pre-receive hook' do
- before do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([false, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('update')
- expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
- end
-
- it 'raises exception' do
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
- end
-
- it 'does not create a new commit inside the repository' do
- current_rev = find_current_rev
-
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
-
- expect(current_rev).to eq find_current_rev
- end
- end
-
- shared_examples 'failing update hook' do
- before do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([false, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
- end
-
- it 'raises exception' do
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
- end
-
- it 'does not create a new commit inside the repository' do
- current_rev = find_current_rev
-
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
-
- expect(current_rev).to eq find_current_rev
- end
- end
-
- shared_examples 'failing post-receive hook' do
- before do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([true, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('post-receive').and_return([false, ''])
- end
-
- it 'does not raise exception' do
- expect { subject.commit }.not_to raise_error
- end
-
- it 'creates the commit' do
- current_rev = find_current_rev
-
- subject.commit
-
- expect(current_rev).not_to eq find_current_rev
- end
- end
-
- shared_examples 'when hooks call succceeds' do
- let(:hook) { double(:hook) }
-
- it 'calls the three hooks' do
- expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
- expect(hook).to receive(:trigger).exactly(3).times.and_return([true, nil])
-
- subject.commit
- end
-
- it 'creates the commit' do
- current_rev = find_current_rev
-
- subject.commit
-
- expect(current_rev).not_to eq find_current_rev
- end
- end
-
- context 'when creating a page' do
- before do
- project_wiki.create_page('index', 'test content')
- end
-
- it_behaves_like 'failing pre-receive hook'
- it_behaves_like 'failing update hook'
- it_behaves_like 'failing post-receive hook'
- it_behaves_like 'when hooks call succceeds'
- end
-
- context 'when updating a page' do
- before do
- project_wiki.update_page(find_page('home'), content: 'some other content', format: :markdown)
- end
-
- it_behaves_like 'failing pre-receive hook'
- it_behaves_like 'failing update hook'
- it_behaves_like 'failing post-receive hook'
- it_behaves_like 'when hooks call succceeds'
- end
-
- context 'when deleting a page' do
- before do
- project_wiki.delete_page(find_page('home'))
- end
-
- it_behaves_like 'failing pre-receive hook'
- it_behaves_like 'failing update hook'
- it_behaves_like 'failing post-receive hook'
- it_behaves_like 'when hooks call succceeds'
- end
-
- def find_current_rev
- wiki.gollum_wiki.repo.commits.first&.sha
- end
-
- def find_page(name)
- wiki.page(title: name)
- end
- end
-
- context 'when Gitaly is enabled' do
- it_behaves_like 'calling wiki hooks'
- end
-
- context 'when Gitaly is disabled', :disable_gitaly do
- it_behaves_like 'calling wiki hooks'
- end
- end
-end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 87d9fcee39e..8a4415506c4 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -2,12 +2,24 @@ require "spec_helper"
describe Gitlab::Git::Diff, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:gitaly_diff) do
+ Gitlab::GitalyClient::Diff.new(
+ from_path: '.gitmodules',
+ to_path: '.gitmodules',
+ old_mode: 0100644,
+ new_mode: 0100644,
+ from_id: '0792c58905eff3432b721f8c4a64363d8e28d9ae',
+ to_id: 'efd587ccb47caf5f31fc954edb21f0a713d9ecc3',
+ overflow_marker: false,
+ collapsed: false,
+ too_large: false,
+ patch: "@@ -4,3 +4,6 @@\n [submodule \"gitlab-shell\"]\n \tpath = gitlab-shell\n \turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n"
+ )
+ end
before do
@raw_diff_hash = {
diff: <<EOT.gsub(/^ {8}/, "").sub(/\n$/, ""),
- --- a/.gitmodules
- +++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "gitlab-shell"]
\tpath = gitlab-shell
@@ -26,12 +38,6 @@ EOT
deleted_file: false,
too_large: false
}
-
- # TODO use a Gitaly diff object instead
- @rugged_diff = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths:
- [".gitmodules"]).patches.first
- end
end
describe '.new' do
@@ -58,9 +64,22 @@ EOT
end
end
- context 'using a Rugged::Patch' do
+ context 'using a GitalyClient::Diff' do
+ let(:gitaly_diff) do
+ Gitlab::GitalyClient::Diff.new(
+ to_path: ".gitmodules",
+ from_path: ".gitmodules",
+ old_mode: 0100644,
+ new_mode: 0100644,
+ from_id: '357406f3075a57708d0163752905cc1576fceacc',
+ to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
+ patch: raw_patch
+ )
+ end
+ let(:diff) { described_class.new(gitaly_diff) }
+
context 'with a small diff' do
- let(:diff) { described_class.new(@rugged_diff) }
+ let(:raw_patch) { @raw_diff_hash[:diff] }
it 'initializes the diff' do
expect(diff.to_hash).to eq(@raw_diff_hash)
@@ -72,28 +91,20 @@ EOT
end
context 'using a diff that is too large' do
- it 'prunes the diff' do
- expect_any_instance_of(String).to receive(:bytesize)
- .and_return(1024 * 1024 * 1024)
-
- diff = described_class.new(@rugged_diff)
+ let(:raw_patch) { 'a' * 204800 }
+ it 'prunes the diff' do
expect(diff.diff).to be_empty
expect(diff).to be_too_large
end
end
context 'using a collapsable diff that is too large' do
- before do
- # The patch total size is 200, with lines between 21 and 54.
- # This is a quick-and-dirty way to test this. Ideally, a new patch is
- # added to the test repo with a size that falls between the real limits.
- stub_const("#{described_class}::SIZE_LIMIT", 150)
- stub_const("#{described_class}::COLLAPSE_LIMIT", 100)
- end
+ let(:raw_patch) { 'a' * 204800 }
it 'prunes the diff as a large diff instead of as a collapsed diff' do
- diff = described_class.new(@rugged_diff, expanded: false)
+ gitaly_diff.too_large = true
+ diff = described_class.new(gitaly_diff, expanded: false)
expect(diff.diff).to be_empty
expect(diff).to be_too_large
@@ -101,54 +112,6 @@ EOT
end
end
- context 'using a large binary diff' do
- it 'does not prune the diff' do
- expect_any_instance_of(Rugged::Diff::Delta).to receive(:binary?)
- .and_return(true)
-
- diff = described_class.new(@rugged_diff)
-
- expect(diff.diff).not_to be_empty
- end
- end
- end
-
- context 'using a GitalyClient::Diff' do
- let(:diff) do
- described_class.new(
- Gitlab::GitalyClient::Diff.new(
- to_path: ".gitmodules",
- from_path: ".gitmodules",
- old_mode: 0100644,
- new_mode: 0100644,
- from_id: '357406f3075a57708d0163752905cc1576fceacc',
- to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
- patch: raw_patch
- )
- )
- end
-
- context 'with a small diff' do
- let(:raw_patch) { @raw_diff_hash[:diff] }
-
- it 'initializes the diff' do
- expect(diff.to_hash).to eq(@raw_diff_hash)
- end
-
- it 'does not prune the diff' do
- expect(diff).not_to be_too_large
- end
- end
-
- context 'using a diff that is too large' do
- let(:raw_patch) { 'a' * 204800 }
-
- it 'prunes the diff' do
- expect(diff.diff).to be_empty
- expect(diff).to be_too_large
- end
- end
-
context 'when the patch passed is not UTF-8-encoded' do
let(:raw_patch) { @raw_diff_hash[:diff].encode(Encoding::ASCII_8BIT) }
@@ -259,31 +222,37 @@ EOT
end
it 'leave non-binary diffs as-is' do
- diff = described_class.new(@rugged_diff)
+ diff = described_class.new(gitaly_diff)
expect(diff.json_safe_diff).to eq(diff.diff)
end
end
describe '#submodule?' do
- before do
- # TODO use a Gitaly diff object instead
- rugged_commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.rev_parse('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
- end
-
- @diffs = rugged_commit.parents[0].diff(rugged_commit).patches
+ let(:gitaly_submodule_diff) do
+ Gitlab::GitalyClient::Diff.new(
+ from_path: 'gitlab-grack',
+ to_path: 'gitlab-grack',
+ old_mode: 0,
+ new_mode: 57344,
+ from_id: '0000000000000000000000000000000000000000',
+ to_id: '645f6c4c82fd3f5e06f67134450a570b795e55a6',
+ overflow_marker: false,
+ collapsed: false,
+ too_large: false,
+ patch: "@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n"
+ )
end
- it { expect(described_class.new(@diffs[0]).submodule?).to eq(false) }
- it { expect(described_class.new(@diffs[1]).submodule?).to eq(true) }
+ it { expect(described_class.new(gitaly_diff).submodule?).to eq(false) }
+ it { expect(described_class.new(gitaly_submodule_diff).submodule?).to eq(true) }
end
describe '#line_count' do
it 'returns the correct number of lines' do
- diff = described_class.new(@rugged_diff)
+ diff = described_class.new(gitaly_diff)
- expect(diff.line_count).to eq(9)
+ expect(diff.line_count).to eq(7)
end
end
diff --git a/spec/lib/gitlab/git/diff_stats_collection_spec.rb b/spec/lib/gitlab/git/diff_stats_collection_spec.rb
new file mode 100644
index 00000000000..b07690ef39c
--- /dev/null
+++ b/spec/lib/gitlab/git/diff_stats_collection_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe Gitlab::Git::DiffStatsCollection do
+ let(:stats_a) do
+ double(Gitaly::DiffStats, additions: 10, deletions: 15, path: 'foo')
+ end
+
+ let(:stats_b) do
+ double(Gitaly::DiffStats, additions: 5, deletions: 1, path: 'bar')
+ end
+
+ let(:diff_stats) { [stats_a, stats_b] }
+ let(:collection) { described_class.new(diff_stats) }
+
+ describe '#find_by_path' do
+ it 'returns stats by path when found' do
+ expect(collection.find_by_path('foo')).to eq(stats_a)
+ end
+
+ it 'returns nil when stats is not found by path' do
+ expect(collection.find_by_path('no-file')).to be_nil
+ end
+ end
+
+ describe '#paths' do
+ it 'returns only modified paths' do
+ expect(collection.paths).to eq %w[foo bar]
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb
deleted file mode 100644
index f5d8503c30c..00000000000
--- a/spec/lib/gitlab/git/gitlab_projects_spec.rb
+++ /dev/null
@@ -1,321 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::GitlabProjects do
- after do
- TestEnv.clean_test_path
- end
-
- around do |example|
- # TODO move this spec to gitaly-ruby. GitlabProjects is not used in gitlab-ce
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- let(:project) { create(:project, :repository) }
-
- if $VERBOSE
- let(:logger) { Logger.new(STDOUT) }
- else
- let(:logger) { double('logger').as_null_object }
- end
-
- 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(TestEnv::REPOS_STORAGE, repo_name) }
-
- describe '#initialize' do
- it { expect(gl_projects.shard_path).to eq(tmp_repos_path) }
- it { expect(gl_projects.repository_relative_path).to eq(repo_name) }
- it { expect(gl_projects.repository_absolute_path).to eq(File.join(tmp_repos_path, repo_name)) }
- it { expect(gl_projects.logger).to eq(logger) }
- end
-
- describe '#push_branches' do
- let(:remote_name) { 'remote-name' }
- let(:branch_name) { 'master' }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} push -- #{remote_name} #{branch_name}) }
- let(:force) { false }
-
- subject { gl_projects.push_branches(remote_name, 600, force, [branch_name]) }
-
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, success: true)
-
- is_expected.to be_truthy
- end
-
- it 'fails' do
- stub_spawn(cmd, 600, tmp_repo_path, success: false)
-
- is_expected.to be_falsy
- end
-
- context 'with --force' do
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} push --force -- #{remote_name} #{branch_name}) }
- let(:force) { true }
-
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, success: true)
-
- is_expected.to be_truthy
- end
- end
- end
-
- describe '#fetch_remote' do
- let(:remote_name) { 'remote-name' }
- let(:branch_name) { 'master' }
- let(:force) { false }
- let(:prune) { true }
- let(:tags) { true }
- let(:args) { { force: force, tags: tags, prune: prune }.merge(extra_args) }
- let(:extra_args) { {} }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --tags) }
-
- subject { gl_projects.fetch_remote(remote_name, 600, args) }
-
- def stub_tempfile(name, filename, opts = {})
- chmod = opts.delete(:chmod)
- file = StringIO.new
-
- allow(file).to receive(:close!)
- allow(file).to receive(:path).and_return(name)
-
- expect(Tempfile).to receive(:new).with(filename).and_return(file)
- expect(file).to receive(:chmod).with(chmod) if chmod
-
- file
- end
-
- context 'with default args' do
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
-
- is_expected.to be_truthy
- end
-
- it 'fails' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: false)
-
- is_expected.to be_falsy
- end
- end
-
- context 'with --force' do
- let(:force) { true }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --force --tags) }
-
- it 'executes the command with forced option' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
-
- is_expected.to be_truthy
- end
- end
-
- context 'with --no-tags' do
- let(:tags) { false }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --no-tags) }
-
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
-
- is_expected.to be_truthy
- end
- end
-
- context 'with no prune' do
- let(:prune) { false }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --tags) }
-
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
-
- is_expected.to be_truthy
- end
- end
-
- describe 'with an SSH key' do
- let(:extra_args) { { ssh_key: 'SSH KEY' } }
-
- it 'sets GIT_SSH to a custom script' do
- script = stub_tempfile('scriptFile', 'gitlab-shell-ssh-wrapper', chmod: 0o755)
- key = stub_tempfile('/tmp files/keyFile', 'gitlab-shell-key-file', chmod: 0o400)
-
- stub_spawn(cmd, 600, tmp_repo_path, { 'GIT_SSH' => 'scriptFile' }, success: true)
-
- is_expected.to be_truthy
-
- expect(script.string).to eq("#!/bin/sh\nexec ssh '-oIdentityFile=\"/tmp files/keyFile\"' '-oIdentitiesOnly=\"yes\"' \"$@\"")
- expect(key.string).to eq('SSH KEY')
- end
- end
-
- describe 'with known_hosts data' do
- let(:extra_args) { { known_hosts: 'KNOWN HOSTS' } }
-
- it 'sets GIT_SSH to a custom script' do
- script = stub_tempfile('scriptFile', 'gitlab-shell-ssh-wrapper', chmod: 0o755)
- key = stub_tempfile('/tmp files/knownHosts', 'gitlab-shell-known-hosts', chmod: 0o400)
-
- stub_spawn(cmd, 600, tmp_repo_path, { 'GIT_SSH' => 'scriptFile' }, success: true)
-
- is_expected.to be_truthy
-
- expect(script.string).to eq("#!/bin/sh\nexec ssh '-oStrictHostKeyChecking=\"yes\"' '-oUserKnownHostsFile=\"/tmp files/knownHosts\"' \"$@\"")
- expect(key.string).to eq('KNOWN HOSTS')
- end
- end
- end
-
- describe '#import_project' do
- let(:project) { create(:project) }
- let(:import_url) { TestEnv.factory_repo_path_bare }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} clone --bare -- #{import_url} #{tmp_repo_path}) }
- let(:timeout) { 600 }
-
- subject { gl_projects.import_project(import_url, timeout) }
-
- shared_examples 'importing repository' do
- context 'success import' do
- it 'imports a repo' do
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
-
- is_expected.to be_truthy
-
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy
- end
- end
-
- context 'already exists' do
- it "doesn't import" do
- FileUtils.mkdir_p(tmp_repo_path)
-
- is_expected.to be_falsy
- end
- end
- end
-
- describe 'logging' do
- it 'imports a repo' do
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>."
- expect(logger).to receive(:info).with(message)
-
- subject
- end
- end
-
- context 'timeout' do
- it 'does not import a repo' do
- stub_spawn_timeout(cmd, timeout, nil)
-
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed."
- expect(logger).to receive(:error).with(message)
-
- is_expected.to be_falsy
-
- expect(gl_projects.output).to eq("Timed out\n")
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
- end
- end
-
- it_behaves_like 'importing repository'
- 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, dest_repo_name) }
-
- before do
- FileUtils.mkdir_p(dest_repos_path)
- end
-
- after do
- FileUtils.rm_rf(dest_repos_path)
- end
-
- shared_examples 'forking a repository' do
- it 'forks the repository' do
- is_expected.to be_truthy
-
- expect(File.exist?(dest_repo)).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy
- end
-
- it 'does not fork if a project of the same name already exists' do
- # create a fake project at the intended destination
- FileUtils.mkdir_p(dest_repo)
-
- is_expected.to be_falsy
- end
- end
-
- it_behaves_like 'forking a repository'
-
- # We seem to be stuck to having only one working Gitaly storage in tests, changing
- # 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) { '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
-
- expect(File.exist?(dest_repo)).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy
- end
- end
-
- describe 'log messages' do
- describe 'successful fork' do
- it do
- message = "Forking repository from <#{tmp_repo_path}> to <#{dest_repo}>."
- expect(logger).to receive(:info).with(message)
-
- subject
- end
- end
-
- describe 'failed fork due existing destination' do
- it do
- FileUtils.mkdir_p(dest_repo)
- message = "fork-repository failed: destination repository <#{dest_repo}> already exists."
- expect(logger).to receive(:error).with(message)
-
- subject
- end
- end
- end
- end
-
- def build_gitlab_projects(*args)
- described_class.new(
- *args,
- global_hooks_path: Gitlab.config.gitlab_shell.hooks_path,
- logger: logger
- )
- end
-
- def stub_spawn(*args, success: true)
- exitstatus = success ? 0 : nil
- expect(gl_projects).to receive(:popen_with_timeout).with(*args)
- .and_return(["output", exitstatus])
- end
-
- def stub_spawn_timeout(*args)
- expect(gl_projects).to receive(:popen_with_timeout).with(*args)
- .and_raise(Timeout::Error)
- end
-end
diff --git a/spec/lib/gitlab/git/hook_env_spec.rb b/spec/lib/gitlab/git/hook_env_spec.rb
index e6aa5ad8c90..5e49ea6da7a 100644
--- a/spec/lib/gitlab/git/hook_env_spec.rb
+++ b/spec/lib/gitlab/git/hook_env_spec.rb
@@ -4,11 +4,7 @@ describe Gitlab::Git::HookEnv do
let(:gl_repository) { 'project-123' }
describe ".set" do
- context 'with RequestStore.store disabled' do
- before do
- allow(RequestStore).to receive(:active?).and_return(false)
- end
-
+ context 'with RequestStore disabled' do
it 'does not store anything' do
described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
@@ -16,11 +12,7 @@ describe Gitlab::Git::HookEnv do
end
end
- context 'with RequestStore.store enabled' do
- before do
- allow(RequestStore).to receive(:active?).and_return(true)
- end
-
+ context 'with RequestStore enabled', :request_store do
it 'whitelist some `GIT_*` variables and stores them using RequestStore' do
described_class.set(
gl_repository,
@@ -41,9 +33,8 @@ describe Gitlab::Git::HookEnv do
end
describe ".all" do
- context 'with RequestStore.store enabled' do
+ context 'with RequestStore enabled', :request_store do
before do
- allow(RequestStore).to receive(:active?).and_return(true)
described_class.set(
gl_repository,
GIT_OBJECT_DIRECTORY_RELATIVE: 'foo',
@@ -60,7 +51,7 @@ describe Gitlab::Git::HookEnv do
end
describe ".to_env_hash" do
- context 'with RequestStore.store enabled' do
+ context 'with RequestStore enabled', :request_store do
using RSpec::Parameterized::TableSyntax
let(:key) { 'GIT_OBJECT_DIRECTORY_RELATIVE' }
@@ -76,7 +67,6 @@ describe Gitlab::Git::HookEnv do
with_them do
before do
- allow(RequestStore).to receive(:active?).and_return(true)
described_class.set(gl_repository, key.to_sym => input)
end
@@ -92,7 +82,7 @@ describe Gitlab::Git::HookEnv do
end
describe 'thread-safety' do
- context 'with RequestStore.store enabled' do
+ context 'with RequestStore enabled', :request_store do
before do
allow(RequestStore).to receive(:active?).and_return(true)
described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
deleted file mode 100644
index a45c8510b15..00000000000
--- a/spec/lib/gitlab/git/hook_spec.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require 'spec_helper'
-require 'fileutils'
-
-describe Gitlab::Git::Hook do
- before do
- # We need this because in the spec/spec_helper.rb we define it like this:
- # allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
- allow_any_instance_of(described_class).to receive(:trigger).and_call_original
- end
-
- around do |example|
- # TODO move hook tests to gitaly-ruby. Hook will disappear from gitlab-ce
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- describe "#trigger" do
- set(:project) { create(:project, :repository) }
- let(:repository) { project.repository.raw_repository }
- let(:repo_path) { repository.path }
- let(:hooks_dir) { File.join(repo_path, 'hooks') }
- let(:user) { create(:user) }
- let(:gl_id) { Gitlab::GlId.gl_id(user) }
- let(:gl_username) { user.username }
-
- def create_hook(name)
- FileUtils.mkdir_p(hooks_dir)
- hook_path = File.join(hooks_dir, name)
- File.open(hook_path, 'w', 0755) do |f|
- f.write(<<~HOOK)
- #!/bin/sh
- exit 0
- HOOK
- end
- end
-
- def create_failing_hook(name)
- FileUtils.mkdir_p(hooks_dir)
- hook_path = File.join(hooks_dir, name)
- File.open(hook_path, 'w', 0755) do |f|
- f.write(<<~HOOK)
- #!/bin/sh
- echo 'regular message from the hook'
- echo 'error message from the hook' 1>&2
- echo 'error message from the hook line 2' 1>&2
- exit 1
- HOOK
- end
- end
-
- ['pre-receive', 'post-receive', 'update'].each do |hook_name|
- context "when triggering a #{hook_name} hook" do
- context "when the hook is successful" do
- let(:hook_path) { File.join(hooks_dir, hook_name) }
- let(:gl_repository) { Gitlab::GlRepository.gl_repository(project, false) }
- let(:env) do
- {
- 'GL_ID' => gl_id,
- 'GL_USERNAME' => gl_username,
- 'PWD' => repo_path,
- 'GL_PROTOCOL' => 'web',
- 'GL_REPOSITORY' => gl_repository
- }
- end
-
- it "returns success with no errors" do
- create_hook(hook_name)
- hook = described_class.new(hook_name, repository)
- blank = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
-
- if hook_name != 'update'
- expect(Open3).to receive(:popen3)
- .with(env, hook_path, chdir: repo_path).and_call_original
- end
-
- status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
- expect(status).to be true
- expect(errors).to be_blank
- end
- end
-
- context "when the hook is unsuccessful" do
- it "returns failure with errors" do
- create_failing_hook(hook_name)
- hook = described_class.new(hook_name, repository)
- blank = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
-
- status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
- expect(status).to be false
- expect(errors).to eq("error message from the hook\nerror message from the hook line 2\n")
- end
- end
- end
- end
-
- context "when the hook doesn't exist" do
- it "returns success with no errors" do
- hook = described_class.new('unknown_hook', repository)
- blank = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
-
- status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
- expect(status).to be true
- expect(errors).to be_nil
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/git/hooks_service_spec.rb b/spec/lib/gitlab/git/hooks_service_spec.rb
deleted file mode 100644
index 55ffced36ac..00000000000
--- a/spec/lib/gitlab/git/hooks_service_spec.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::HooksService, :seed_helper do
- let(:gl_id) { 'user-456' }
- let(:gl_username) { 'janedoe' }
- let(:user) { Gitlab::Git::User.new(gl_username, 'Jane Doe', 'janedoe@example.com', gl_id) }
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, 'project-123') }
- let(:service) { described_class.new }
- let(:blankrev) { Gitlab::Git::BLANK_SHA }
- let(:oldrev) { SeedRepo::Commit::PARENT_ID }
- let(:newrev) { SeedRepo::Commit::ID }
- let(:ref) { 'refs/heads/feature' }
-
- describe '#execute' do
- context 'when receive hooks were successful' do
- let(:hook) { double(:hook) }
-
- it 'calls all three hooks' do
- expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
- expect(hook).to receive(:trigger).with(gl_id, gl_username, blankrev, newrev, ref)
- .exactly(3).times.and_return([true, nil])
-
- service.execute(user, repository, blankrev, newrev, ref) { }
- end
- end
-
- context 'when pre-receive hook failed' do
- it 'does not call post-receive hook' do
- expect(service).to receive(:run_hook).with('pre-receive').and_return([false, 'hello world'])
- expect(service).not_to receive(:run_hook).with('post-receive')
-
- expect do
- service.execute(user, repository, blankrev, newrev, ref)
- end.to raise_error(Gitlab::Git::PreReceiveError, 'hello world')
- end
- end
-
- context 'when update hook failed' do
- it 'does not call post-receive hook' do
- expect(service).to receive(:run_hook).with('pre-receive').and_return([true, nil])
- expect(service).to receive(:run_hook).with('update').and_return([false, 'hello world'])
- expect(service).not_to receive(:run_hook).with('post-receive')
-
- expect do
- service.execute(user, repository, blankrev, newrev, ref)
- end.to raise_error(Gitlab::Git::PreReceiveError, 'hello world')
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/git/index_spec.rb b/spec/lib/gitlab/git/index_spec.rb
deleted file mode 100644
index c4edd6961e1..00000000000
--- a/spec/lib/gitlab/git/index_spec.rb
+++ /dev/null
@@ -1,239 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Index, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
- let(:index) { described_class.new(repository) }
-
- before do
- index.read_tree(lookup('master').tree)
- end
-
- around do |example|
- # TODO move these specs to gitaly-ruby. The Index class will disappear from gitlab-ce
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- describe '#create' do
- let(:options) do
- {
- content: 'Lorem ipsum...',
- file_path: 'documents/story.txt'
- }
- end
-
- context 'when no file at that path exists' do
- it 'creates the file in the index' do
- index.create(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry).not_to be_nil
- expect(lookup(entry[:oid]).content).to eq(options[:content])
- end
- end
-
- context 'when a file at that path exists' do
- before do
- options[:file_path] = 'files/executables/ls'
- end
-
- it 'raises an error' do
- expect { index.create(options) }.to raise_error('A file with this name already exists')
- end
- end
-
- context 'when content is in base64' do
- before do
- options[:content] = Base64.encode64(options[:content])
- options[:encoding] = 'base64'
- end
-
- it 'decodes base64' do
- index.create(options)
-
- entry = index.get(options[:file_path])
- expect(lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content]))
- end
- end
-
- context 'when content contains CRLF' do
- before do
- repository.autocrlf = :input
- options[:content] = "Hello,\r\nWorld"
- end
-
- it 'converts to LF' do
- index.create(options)
-
- entry = index.get(options[:file_path])
- expect(lookup(entry[:oid]).content).to eq("Hello,\nWorld")
- end
- end
- end
-
- describe '#create_dir' do
- let(:options) do
- {
- file_path: 'newdir'
- }
- end
-
- context 'when no file or dir at that path exists' do
- it 'creates the dir in the index' do
- index.create_dir(options)
-
- entry = index.get(options[:file_path] + '/.gitkeep')
-
- expect(entry).not_to be_nil
- end
- end
-
- context 'when a file at that path exists' do
- before do
- options[:file_path] = 'files/executables/ls'
- end
-
- it 'raises an error' do
- expect { index.create_dir(options) }.to raise_error('A file with this name already exists')
- end
- end
-
- context 'when a directory at that path exists' do
- before do
- options[:file_path] = 'files/executables'
- end
-
- it 'raises an error' do
- expect { index.create_dir(options) }.to raise_error('A directory with this name already exists')
- end
- end
- end
-
- describe '#update' do
- let(:options) do
- {
- content: 'Lorem ipsum...',
- file_path: 'README.md'
- }
- end
-
- context 'when no file at that path exists' do
- before do
- options[:file_path] = 'documents/story.txt'
- end
-
- it 'raises an error' do
- expect { index.update(options) }.to raise_error("A file with this name doesn't exist")
- end
- end
-
- context 'when a file at that path exists' do
- it 'updates the file in the index' do
- index.update(options)
-
- entry = index.get(options[:file_path])
-
- expect(lookup(entry[:oid]).content).to eq(options[:content])
- end
-
- it 'preserves file mode' do
- options[:file_path] = 'files/executables/ls'
-
- index.update(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry[:mode]).to eq(0100755)
- end
- end
- end
-
- describe '#move' do
- let(:options) do
- {
- content: 'Lorem ipsum...',
- previous_path: 'README.md',
- file_path: 'NEWREADME.md'
- }
- end
-
- context 'when no file at that path exists' do
- it 'raises an error' do
- options[:previous_path] = 'documents/story.txt'
-
- expect { index.move(options) }.to raise_error("A file with this name doesn't exist")
- end
- end
-
- context 'when a file at the new path already exists' do
- it 'raises an error' do
- options[:file_path] = 'CHANGELOG'
-
- expect { index.move(options) }.to raise_error("A file with this name already exists")
- end
- end
-
- context 'when a file at that path exists' do
- it 'removes the old file in the index' do
- index.move(options)
-
- entry = index.get(options[:previous_path])
-
- expect(entry).to be_nil
- end
-
- it 'creates the new file in the index' do
- index.move(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry).not_to be_nil
- expect(lookup(entry[:oid]).content).to eq(options[:content])
- end
-
- it 'preserves file mode' do
- options[:previous_path] = 'files/executables/ls'
-
- index.move(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry[:mode]).to eq(0100755)
- end
- end
- end
-
- describe '#delete' do
- let(:options) do
- {
- file_path: 'README.md'
- }
- end
-
- context 'when no file at that path exists' do
- before do
- options[:file_path] = 'documents/story.txt'
- end
-
- it 'raises an error' do
- expect { index.delete(options) }.to raise_error("A file with this name doesn't exist")
- end
- end
-
- context 'when a file at that path exists' do
- it 'removes the file in the index' do
- index.delete(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry).to be_nil
- end
- end
- end
-
- def lookup(revision)
- repository.rugged.rev_parse(revision)
- end
-end
diff --git a/spec/lib/gitlab/git/popen_spec.rb b/spec/lib/gitlab/git/popen_spec.rb
deleted file mode 100644
index 074e66d2a5d..00000000000
--- a/spec/lib/gitlab/git/popen_spec.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-require 'spec_helper'
-
-describe 'Gitlab::Git::Popen' do
- let(:path) { Rails.root.join('tmp').to_s }
- let(:test_string) { 'The quick brown fox jumped over the lazy dog' }
- # The pipe buffer is typically 64K. This string is about 440K.
- let(:spew_command) { ['bash', '-c', "for i in {1..10000}; do echo '#{test_string}' 1>&2; done"] }
-
- let(:klass) do
- Class.new(Object) do
- include Gitlab::Git::Popen
- end
- end
-
- context 'popen' do
- context 'zero status' do
- let(:result) { klass.new.popen(%w(ls), path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to be_zero }
- it { expect(output).to include('tests') }
- end
-
- context 'non-zero status' do
- let(:result) { klass.new.popen(%w(cat NOTHING), path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to eq(1) }
- it { expect(output).to include('No such file or directory') }
- end
-
- context 'unsafe string command' do
- it 'raises an error when it gets called with a string argument' do
- expect { klass.new.popen('ls', path) }.to raise_error(RuntimeError)
- end
- end
-
- context 'with custom options' do
- let(:vars) { { 'foobar' => 123, 'PWD' => path } }
- let(:options) { { chdir: path } }
-
- it 'calls popen3 with the provided environment variables' do
- expect(Open3).to receive(:popen3).with(vars, 'ls', options)
-
- klass.new.popen(%w(ls), path, { 'foobar' => 123 })
- end
- end
-
- context 'use stdin' do
- let(:result) { klass.new.popen(%w[cat], path) { |stdin| stdin.write 'hello' } }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to be_zero }
- it { expect(output).to eq('hello') }
- end
-
- context 'with lazy block' do
- it 'yields a lazy io' do
- expect_lazy_io = lambda do |io|
- expect(io).to be_a Enumerator::Lazy
- expect(io.inspect).to include('#<IO:fd')
- end
-
- klass.new.popen(%w[ls], path, lazy_block: expect_lazy_io)
- end
-
- it "doesn't wait for process exit" do
- Timeout.timeout(2) do
- klass.new.popen(%w[yes], path, lazy_block: ->(io) {})
- end
- end
- end
-
- context 'with a process that writes a lot of data to stderr' do
- it 'returns zero' do
- output, status = klass.new.popen(spew_command, path)
-
- expect(output).to include(test_string)
- expect(status).to eq(0)
- end
- end
- end
-
- context 'popen_with_timeout' do
- let(:timeout) { 1.second }
-
- context 'no timeout' do
- context 'zero status' do
- let(:result) { klass.new.popen_with_timeout(%w(ls), timeout, path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to be_zero }
- it { expect(output).to include('tests') }
- end
-
- context 'multi-line string' do
- let(:test_string) { "this is 1 line\n2nd line\n3rd line\n" }
- let(:result) { klass.new.popen_with_timeout(['echo', test_string], timeout, path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to be_zero }
- # echo adds its own line
- it { expect(output).to eq(test_string + "\n") }
- end
-
- context 'non-zero status' do
- let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to eq(1) }
- it { expect(output).to include('No such file or directory') }
- end
-
- context 'unsafe string command' do
- it 'raises an error when it gets called with a string argument' do
- expect { klass.new.popen_with_timeout('ls', timeout, path) }.to raise_error(RuntimeError)
- end
- end
- end
-
- context 'timeout' do
- context 'timeout' do
- it "raises a Timeout::Error" do
- expect { klass.new.popen_with_timeout(%w(sleep 1000), timeout, path) }.to raise_error(Timeout::Error)
- end
-
- it "handles processes that do not shutdown correctly" do
- expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error)
- end
-
- it 'handles process that writes a lot of data to stderr' do
- output, status = klass.new.popen_with_timeout(spew_command, timeout, path)
-
- expect(output).to include(test_string)
- expect(status).to eq(0)
- end
- end
-
- context 'timeout period' do
- let(:time_taken) do
- begin
- start = Time.now
- klass.new.popen_with_timeout(%w(sleep 1000), timeout, path)
- rescue
- Time.now - start
- end
- end
-
- it { expect(time_taken).to be >= timeout }
- end
-
- context 'clean up' do
- let(:instance) { klass.new }
-
- it 'kills the child process' do
- expect(instance).to receive(:kill_process_group_for_pid).and_wrap_original do |m, *args|
- # is the PID, and it should not be running at this point
- m.call(*args)
-
- pid = args.first
- begin
- Process.getpgid(pid)
- raise "The child process should have been killed"
- rescue Errno::ESRCH
- end
- end
-
- expect { instance.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error)
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb
new file mode 100644
index 00000000000..566c8209504
--- /dev/null
+++ b/spec/lib/gitlab/git/push_spec.rb
@@ -0,0 +1,166 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Push do
+ set(:project) { create(:project, :repository) }
+
+ let(:oldrev) { project.commit('HEAD~2').id }
+ let(:newrev) { project.commit.id }
+ let(:ref) { 'refs/heads/some-branch' }
+
+ subject { described_class.new(project, oldrev, newrev, ref) }
+
+ describe '#branch_name' do
+ context 'when it is a branch push' do
+ let(:ref) { 'refs/heads/my-branch' }
+
+ it 'returns branch name' do
+ expect(subject.branch_name).to eq 'my-branch'
+ end
+ end
+
+ context 'when it is a tag push' do
+ let(:ref) { 'refs/tags/my-branch' }
+
+ it 'returns nil' do
+ expect(subject.branch_name).to be_nil
+ end
+ end
+ end
+
+ describe '#branch_push?' do
+ context 'when pushing a branch ref' do
+ let(:ref) { 'refs/heads/my-branch' }
+
+ it { is_expected.to be_branch_push }
+ end
+
+ context 'when it is a tag push' do
+ let(:ref) { 'refs/tags/my-tag' }
+
+ it { is_expected.not_to be_branch_push }
+ end
+ end
+
+ describe '#branch_updated?' do
+ context 'when it is a branch push with correct old and new revisions' do
+ it { is_expected.to be_branch_updated }
+ end
+
+ context 'when it is not a branch push' do
+ let(:ref) { 'refs/tags/my-tag' }
+
+ it { is_expected.not_to be_branch_updated }
+ end
+
+ context 'when old revision is blank' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+
+ it { is_expected.not_to be_branch_updated }
+ end
+
+ context 'when it is not a branch push' do
+ let(:newrev) { Gitlab::Git::BLANK_SHA }
+
+ it { is_expected.not_to be_branch_updated }
+ end
+
+ context 'when oldrev is nil' do
+ let(:oldrev) { nil }
+
+ it { is_expected.not_to be_branch_updated }
+ end
+ end
+
+ describe '#force_push?' do
+ context 'when old revision is an ancestor of the new revision' do
+ let(:oldrev) { 'HEAD~3' }
+ let(:newrev) { 'HEAD~1' }
+
+ it { is_expected.not_to be_force_push }
+ end
+
+ context 'when old revision is not an ancestor of the new revision' do
+ let(:oldrev) { 'HEAD~3' }
+ let(:newrev) { '123456' }
+
+ it { is_expected.to be_force_push }
+ end
+ end
+
+ describe '#branch_added?' do
+ context 'when old revision is defined' do
+ it { is_expected.not_to be_branch_added }
+ end
+
+ context 'when old revision is not defined' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+
+ it { is_expected.to be_branch_added }
+ end
+ end
+
+ describe '#branch_removed?' do
+ context 'when new revision is defined' do
+ it { is_expected.not_to be_branch_removed }
+ end
+
+ context 'when new revision is not defined' do
+ let(:newrev) { Gitlab::Git::BLANK_SHA }
+
+ it { is_expected.to be_branch_removed }
+ end
+ end
+
+ describe '#modified_paths' do
+ context 'when a push is a branch update' do
+ let(:newrev) { '498214d' }
+ let(:oldrev) { '281d3a7' }
+
+ it 'returns modified paths' do
+ expect(subject.modified_paths).to eq ['bar/branch-test.txt',
+ 'files/js/commit.coffee',
+ 'with space/README.md']
+ end
+ end
+
+ context 'when a push is not a branch update' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+
+ it 'raises an error' do
+ expect { subject.modified_paths }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe '#oldrev' do
+ context 'when a valid oldrev is provided' do
+ it 'returns oldrev' do
+ expect(subject.oldrev).to eq oldrev
+ end
+ end
+
+ context 'when a nil valud is provided' do
+ let(:oldrev) { nil }
+
+ it 'returns blank SHA' do
+ expect(subject.oldrev).to eq Gitlab::Git::BLANK_SHA
+ end
+ end
+ end
+
+ describe '#newrev' do
+ context 'when valid newrev is provided' do
+ it 'returns newrev' do
+ expect(subject.newrev).to eq newrev
+ end
+ end
+
+ context 'when a nil valud is provided' do
+ let(:newrev) { nil }
+
+ it 'returns blank SHA' do
+ expect(subject.newrev).to eq Gitlab::Git::BLANK_SHA
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 17348b01006..51eb997a325 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -19,7 +19,10 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ let(:mutable_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) }
+ let(:repository_rugged) { Rugged::Repository.new(repository_path) }
let(:storage_path) { TestEnv.repos_path }
let(:user) { build(:user) }
@@ -71,7 +74,6 @@ describe Gitlab::Git::Repository, :seed_helper do
describe "Respond to" do
subject { repository }
- it { is_expected.to respond_to(:rugged) }
it { is_expected.to respond_to(:root_ref) }
it { is_expected.to respond_to(:tags) }
end
@@ -91,57 +93,6 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- describe "#rugged" do
- describe 'when storage is broken', :broken_storage do
- it 'raises a storage exception when storage is not available' do
- broken_repo = described_class.new('broken', 'a/path.git', '')
-
- expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Storage::Inaccessible)
- end
- end
-
- it 'raises a no repository exception when there is no repo' do
- broken_repo = described_class.new('default', 'a/path.git', '')
-
- expect do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access { broken_repo.rugged }
- end.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
-
- describe 'alternates keyword argument' do
- context 'with no Git env stored' do
- before do
- allow(Gitlab::Git::HookEnv).to receive(:all).and_return({})
- end
-
- it "is passed an empty array" do
- expect(Rugged::Repository).to receive(:new).with(repository_path, alternates: [])
-
- repository_rugged
- end
- end
-
- context 'with absolute and relative Git object dir envvars stored' do
- before do
- 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',
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => %w[ignored ignored],
- 'GIT_OTHER' => 'another_env'
- })
- end
-
- it "is passed the relative object dir envvars after being converted to absolute ones" do
- alternates = %w[foo bar baz].map { |d| File.join(repository_path, './objects', d) }
- expect(Rugged::Repository).to receive(:new).with(repository_path, alternates: alternates)
-
- repository_rugged
- end
- end
- end
- end
-
describe '#branch_names' do
subject { repository.branch_names }
@@ -284,7 +235,6 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#submodule_url_for' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
let(:ref) { 'master' }
def submodule_url(path)
@@ -322,21 +272,12 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#commit_count' do
- shared_examples 'simple commit counting' do
- it { expect(repository.commit_count("master")).to eq(25) }
- it { expect(repository.commit_count("feature")).to eq(9) }
- it { expect(repository.commit_count("does-not-exist")).to eq(0) }
- end
+ it { expect(repository.commit_count("master")).to eq(25) }
+ it { expect(repository.commit_count("feature")).to eq(9) }
+ it { expect(repository.commit_count("does-not-exist")).to eq(0) }
- context 'when Gitaly commit_count feature is enabled' do
- it_behaves_like 'simple commit counting'
- it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do
- subject { repository.commit_count('master') }
- end
- end
-
- context 'when Gitaly commit_count feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'simple commit counting'
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do
+ subject { repository.commit_count('master') }
end
end
@@ -345,7 +286,7 @@ describe Gitlab::Git::Repository, :seed_helper do
it { expect(repository.has_local_branches?).to eq(true) }
context 'mutable' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+ let(:repository) { mutable_repository }
after do
ensure_seeds
@@ -378,118 +319,82 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe "#delete_branch" do
- shared_examples "deleting a branch" do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
-
- after do
- ensure_seeds
- end
-
- it "removes the branch from the repo" do
- branch_name = "to-be-deleted-soon"
+ let(:repository) { mutable_repository }
- repository.create_branch(branch_name)
- expect(repository_rugged.branches[branch_name]).not_to be_nil
+ after do
+ ensure_seeds
+ end
- repository.delete_branch(branch_name)
- expect(repository_rugged.branches[branch_name]).to be_nil
- end
+ it "removes the branch from the repo" do
+ branch_name = "to-be-deleted-soon"
- context "when branch does not exist" do
- it "raises a DeleteBranchError exception" do
- expect { repository.delete_branch("this-branch-does-not-exist") }.to raise_error(Gitlab::Git::Repository::DeleteBranchError)
- end
- end
- end
+ repository.create_branch(branch_name)
+ expect(repository_rugged.branches[branch_name]).not_to be_nil
- context "when Gitaly delete_branch is enabled" do
- it_behaves_like "deleting a branch"
+ repository.delete_branch(branch_name)
+ expect(repository_rugged.branches[branch_name]).to be_nil
end
- context "when Gitaly delete_branch is disabled", :skip_gitaly_mock do
- it_behaves_like "deleting a branch"
+ context "when branch does not exist" do
+ it "raises a DeleteBranchError exception" do
+ expect { repository.delete_branch("this-branch-does-not-exist") }.to raise_error(Gitlab::Git::Repository::DeleteBranchError)
+ end
end
end
describe "#create_branch" do
- shared_examples 'creating a branch' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
-
- after do
- ensure_seeds
- end
-
- it "should create a new branch" do
- expect(repository.create_branch('new_branch', 'master')).not_to be_nil
- end
+ let(:repository) { mutable_repository }
- it "should create a new branch with the right name" do
- expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch')
- end
+ after do
+ ensure_seeds
+ end
- it "should fail if we create an existing branch" do
- repository.create_branch('duplicated_branch', 'master')
- expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
- end
+ it "should create a new branch" do
+ expect(repository.create_branch('new_branch', 'master')).not_to be_nil
+ end
- it "should fail if we create a branch from a non existing ref" do
- expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
- end
+ it "should create a new branch with the right name" do
+ expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch')
end
- context 'when Gitaly create_branch feature is enabled' do
- it_behaves_like 'creating a branch'
+ it "should fail if we create an existing branch" do
+ repository.create_branch('duplicated_branch', 'master')
+ expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
end
- context 'when Gitaly create_branch feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'creating a branch'
+ it "should fail if we create a branch from a non existing ref" do
+ expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
end
end
describe '#delete_refs' do
- shared_examples 'deleting refs' do
- let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
-
- def repo_rugged
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repo.rugged
- end
- end
-
- after do
- ensure_seeds
- end
-
- it 'deletes the ref' do
- repo.delete_refs('refs/heads/feature')
+ let(:repository) { mutable_repository }
- expect(repo_rugged.references['refs/heads/feature']).to be_nil
- end
+ after do
+ ensure_seeds
+ end
- it 'deletes all refs' do
- refs = %w[refs/heads/wip refs/tags/v1.1.0]
- repo.delete_refs(*refs)
+ it 'deletes the ref' do
+ repository.delete_refs('refs/heads/feature')
- refs.each do |ref|
- expect(repo_rugged.references[ref]).to be_nil
- end
- end
+ expect(repository_rugged.references['refs/heads/feature']).to be_nil
+ end
- it 'does not fail when deleting an empty list of refs' do
- expect { repo.delete_refs(*[]) }.not_to raise_error
- end
+ it 'deletes all refs' do
+ refs = %w[refs/heads/wip refs/tags/v1.1.0]
+ repository.delete_refs(*refs)
- it 'raises an error if it failed' do
- expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
+ refs.each do |ref|
+ expect(repository_rugged.references[ref]).to be_nil
end
end
- context 'when Gitaly delete_refs feature is enabled' do
- it_behaves_like 'deleting refs'
+ it 'does not fail when deleting an empty list of refs' do
+ expect { repository.delete_refs(*[]) }.not_to raise_error
end
- context 'when Gitaly delete_refs feature is disabled', :disable_gitaly do
- it_behaves_like 'deleting refs'
+ it 'raises an error if it failed' do
+ expect { repository.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
end
end
@@ -542,44 +447,63 @@ describe Gitlab::Git::Repository, :seed_helper do
Gitlab::Shell.new.remove_repository('default', 'my_project')
end
- shared_examples 'repository mirror fetching' do
- it 'fetches a repository as a mirror remote' do
+ it 'fetches a repository as a mirror remote' do
+ subject
+
+ expect(refs(new_repository_path)).to eq(refs(repository_path))
+ end
+
+ context 'with keep-around refs' do
+ let(:sha) { SeedRepo::Commit::ID }
+ let(:keep_around_ref) { "refs/keep-around/#{sha}" }
+ let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
+
+ before do
+ repository_rugged.references.create(keep_around_ref, sha, force: true)
+ repository_rugged.references.create(tmp_ref, sha, force: true)
+ end
+
+ it 'includes the temporary and keep-around refs' do
subject
- expect(refs(new_repository_path)).to eq(refs(repository_path))
+ expect(refs(new_repository_path)).to include(keep_around_ref)
+ expect(refs(new_repository_path)).to include(tmp_ref)
end
+ end
- context 'with keep-around refs' do
- let(:sha) { SeedRepo::Commit::ID }
- let(:keep_around_ref) { "refs/keep-around/#{sha}" }
- let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
+ def new_repository_path
+ File.join(TestEnv.repos_path, new_repository.relative_path)
+ end
+ end
- before do
- repository_rugged.references.create(keep_around_ref, sha, force: true)
- repository_rugged.references.create(tmp_ref, sha, force: true)
- end
+ describe '#find_remote_root_ref' do
+ it 'gets the remote root ref from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
+ .to receive(:find_remote_root_ref).and_call_original
- it 'includes the temporary and keep-around refs' do
- subject
+ expect(repository.find_remote_root_ref('origin')).to eq 'master'
+ end
- expect(refs(new_repository_path)).to include(keep_around_ref)
- expect(refs(new_repository_path)).to include(tmp_ref)
- end
- end
+ it 'returns UTF-8' do
+ expect(repository.find_remote_root_ref('origin')).to be_utf8
end
- context 'with gitaly enabled' do
- it_behaves_like 'repository mirror fetching'
+ it 'returns nil when remote name is nil' do
+ expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
+ .not_to receive(:find_remote_root_ref)
+
+ expect(repository.find_remote_root_ref(nil)).to be_nil
end
- context 'with gitaly enabled', :skip_gitaly_mock do
- it_behaves_like 'repository mirror fetching'
+ it 'returns nil when remote name is empty' do
+ expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
+ .not_to receive(:find_remote_root_ref)
+
+ expect(repository.find_remote_root_ref('')).to be_nil
end
- def new_repository_path
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- new_repository.path
- end
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RemoteService, :find_remote_root_ref do
+ subject { repository.find_remote_root_ref('origin') }
end
end
@@ -595,18 +519,16 @@ describe Gitlab::Git::Repository, :seed_helper do
Gitlab::Git::Commit.find(repository, @rename_commit_id)
end
- before(:context) do
+ before do
# Add new commits so that there's a renamed file in the commit history
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- @commit_with_old_name_id = new_commit_edit_old_file(repo).oid
- @rename_commit_id = new_commit_move_file(repo).oid
- @commit_with_new_name_id = new_commit_edit_new_file(repo).oid
+ @commit_with_old_name_id = new_commit_edit_old_file(repository_rugged).oid
+ @rename_commit_id = new_commit_move_file(repository_rugged).oid
+ @commit_with_new_name_id = new_commit_edit_new_file(repository_rugged).oid
end
- after(:context) do
+ after do
# Erase our commits so other tests get the original repo
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
+ repository_rugged.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
end
context "where 'follow' == true" do
@@ -887,25 +809,15 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#merge_base' do
- shared_examples '#merge_base' do
- where(:from, :to, :result) do
- '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
- '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
- '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil
- 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil
- end
-
- with_them do
- it { expect(repository.merge_base(from, to)).to eq(result) }
- end
- end
-
- context 'with gitaly' do
- it_behaves_like '#merge_base'
+ where(:from, :to, :result) do
+ '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil
+ 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil
end
- context 'without gitaly', :skip_gitaly_mock do
- it_behaves_like '#merge_base'
+ with_them do
+ it { expect(repository.merge_base(from, to)).to eq(result) }
end
end
@@ -997,54 +909,6 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- describe '#autocrlf' do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- @repo.rugged.config['core.autocrlf'] = true
- end
-
- around do |example|
- # OK because autocrlf is only used in gitaly-ruby
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- it 'return the value of the autocrlf option' do
- expect(@repo.autocrlf).to be(true)
- end
-
- after(:all) do
- @repo.rugged.config.delete('core.autocrlf')
- end
- end
-
- describe '#autocrlf=' do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- @repo.rugged.config['core.autocrlf'] = false
- end
-
- around do |example|
- # OK because autocrlf= is only used in gitaly-ruby
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- it 'should set the autocrlf option to the provided option' do
- @repo.autocrlf = :input
-
- File.open(File.join(SEED_STORAGE_PATH, TEST_MUTABLE_REPO_PATH, 'config')) do |config_file|
- expect(config_file.read).to match('autocrlf = input')
- end
- end
-
- after(:all) do
- @repo.rugged.config.delete('core.autocrlf')
- end
- end
-
describe '#find_branch' do
it 'should return a Branch for master' do
branch = repository.find_branch('master')
@@ -1086,12 +950,10 @@ describe Gitlab::Git::Repository, :seed_helper do
subject { repository.branches }
context 'with local and remote branches' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
before do
- create_remote_branch(repository, 'joe', 'remote_branch', 'master')
+ create_remote_branch('joe', 'remote_branch', 'master')
repository.create_branch('local_branch', 'master')
end
@@ -1114,12 +976,10 @@ describe Gitlab::Git::Repository, :seed_helper do
end
context 'with local and remote branches' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
before do
- create_remote_branch(repository, 'joe', 'remote_branch', 'master')
+ create_remote_branch('joe', 'remote_branch', 'master')
repository.create_branch('local_branch', 'master')
end
@@ -1144,57 +1004,81 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#merged_branch_names' do
- shared_examples 'finding merged branch names' do
- context 'when branch names are passed' do
- it 'only returns the names we are asking' do
- names = repository.merged_branch_names(%w[merge-test])
+ context 'when branch names are passed' do
+ it 'only returns the names we are asking' do
+ names = repository.merged_branch_names(%w[merge-test])
- expect(names).to contain_exactly('merge-test')
- end
+ expect(names).to contain_exactly('merge-test')
+ end
- it 'does not return unmerged branch names' do
- names = repository.merged_branch_names(%w[feature])
+ it 'does not return unmerged branch names' do
+ names = repository.merged_branch_names(%w[feature])
- expect(names).to be_empty
- end
+ expect(names).to be_empty
end
+ end
- context 'when no root ref is available' do
- it 'returns empty list' do
- project = create(:project, :empty_repo)
+ context 'when no root ref is available' do
+ it 'returns empty list' do
+ project = create(:project, :empty_repo)
- names = project.repository.merged_branch_names(%w[feature])
+ names = project.repository.merged_branch_names(%w[feature])
- expect(names).to be_empty
- end
+ expect(names).to be_empty
end
+ end
- context 'when no branch names are specified' do
- before do
- repository.create_branch('identical', 'master')
- end
+ context 'when no branch names are specified' do
+ before do
+ repository.create_branch('identical', 'master')
+ end
- after do
- ensure_seeds
- end
+ after do
+ ensure_seeds
+ end
- it 'returns all merged branch names except for identical one' do
- names = repository.merged_branch_names
+ it 'returns all merged branch names except for identical one' do
+ names = repository.merged_branch_names
- expect(names).to include('merge-test')
- expect(names).to include('fix-mode')
- expect(names).not_to include('feature')
- expect(names).not_to include('identical')
- end
+ expect(names).to include('merge-test')
+ expect(names).to include('fix-mode')
+ expect(names).not_to include('feature')
+ expect(names).not_to include('identical')
end
end
+ end
+
+ describe '#diff_stats' do
+ let(:left_commit_id) { 'feature' }
+ let(:right_commit_id) { 'master' }
+
+ it 'returns a DiffStatsCollection' do
+ collection = repository.diff_stats(left_commit_id, right_commit_id)
+
+ expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
+ expect(collection).to be_a(Enumerable)
+ end
+
+ it 'yields Gitaly::DiffStats objects' do
+ collection = repository.diff_stats(left_commit_id, right_commit_id)
+
+ expect(collection.to_a).to all(be_a(Gitaly::DiffStats))
+ end
+
+ it 'returns no Gitaly::DiffStats when SHAs are invalid' do
+ collection = repository.diff_stats('foo', 'bar')
- context 'when Gitaly merged_branch_names feature is enabled' do
- it_behaves_like 'finding merged branch names'
+ expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
+ expect(collection).to be_a(Enumerable)
+ expect(collection.to_a).to be_empty
end
- context 'when Gitaly merged_branch_names feature is disabled', :disable_gitaly do
- it_behaves_like 'finding merged branch names'
+ it 'returns no Gitaly::DiffStats when there is a nil SHA' do
+ collection = repository.diff_stats(nil, 'master')
+
+ expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
+ expect(collection).to be_a(Enumerable)
+ expect(collection.to_a).to be_empty
end
end
@@ -1311,98 +1195,68 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#ref_exists?' do
- shared_examples 'checks the existence of refs' do
- it 'returns true for an existing tag' do
- expect(repository.ref_exists?('refs/heads/master')).to eq(true)
- end
-
- it 'returns false for a non-existing tag' do
- expect(repository.ref_exists?('refs/tags/THIS_TAG_DOES_NOT_EXIST')).to eq(false)
- end
-
- it 'raises an ArgumentError for an empty string' do
- expect { repository.ref_exists?('') }.to raise_error(ArgumentError)
- end
+ it 'returns true for an existing tag' do
+ expect(repository.ref_exists?('refs/heads/master')).to eq(true)
+ end
- it 'raises an ArgumentError for an invalid ref' do
- expect { repository.ref_exists?('INVALID') }.to raise_error(ArgumentError)
- end
+ it 'returns false for a non-existing tag' do
+ expect(repository.ref_exists?('refs/tags/THIS_TAG_DOES_NOT_EXIST')).to eq(false)
end
- context 'when Gitaly ref_exists feature is enabled' do
- it_behaves_like 'checks the existence of refs'
+ it 'raises an ArgumentError for an empty string' do
+ expect { repository.ref_exists?('') }.to raise_error(ArgumentError)
end
- context 'when Gitaly ref_exists feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'checks the existence of refs'
+ it 'raises an ArgumentError for an invalid ref' do
+ expect { repository.ref_exists?('INVALID') }.to raise_error(ArgumentError)
end
end
describe '#tag_exists?' do
- shared_examples 'checks the existence of tags' do
- it 'returns true for an existing tag' do
- tag = repository.tag_names.first
-
- expect(repository.tag_exists?(tag)).to eq(true)
- end
+ it 'returns true for an existing tag' do
+ tag = repository.tag_names.first
- it 'returns false for a non-existing tag' do
- expect(repository.tag_exists?('v9000')).to eq(false)
- end
+ expect(repository.tag_exists?(tag)).to eq(true)
end
- context 'when Gitaly ref_exists_tags feature is enabled' do
- it_behaves_like 'checks the existence of tags'
- end
-
- context 'when Gitaly ref_exists_tags feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'checks the existence of tags'
+ it 'returns false for a non-existing tag' do
+ expect(repository.tag_exists?('v9000')).to eq(false)
end
end
describe '#branch_exists?' do
- shared_examples 'checks the existence of branches' do
- it 'returns true for an existing branch' do
- expect(repository.branch_exists?('master')).to eq(true)
- end
-
- it 'returns false for a non-existing branch' do
- expect(repository.branch_exists?('kittens')).to eq(false)
- end
-
- it 'returns false when using an invalid branch name' do
- expect(repository.branch_exists?('.bla')).to eq(false)
- end
+ it 'returns true for an existing branch' do
+ expect(repository.branch_exists?('master')).to eq(true)
end
- context 'when Gitaly ref_exists_branches feature is enabled' do
- it_behaves_like 'checks the existence of branches'
+ it 'returns false for a non-existing branch' do
+ expect(repository.branch_exists?('kittens')).to eq(false)
end
- context 'when Gitaly ref_exists_branches feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'checks the existence of branches'
+ it 'returns false when using an invalid branch name' do
+ expect(repository.branch_exists?('.bla')).to eq(false)
end
end
describe '#local_branches' do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
+ let(:repository) { mutable_repository }
+
+ before do
+ create_remote_branch('joe', 'remote_branch', 'master')
+ repository.create_branch('local_branch', 'master')
end
- after(:all) do
+ after do
ensure_seeds
end
it 'returns the local branches' do
- create_remote_branch(@repo, 'joe', 'remote_branch', 'master')
- @repo.create_branch('local_branch', 'master')
-
- expect(@repo.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
- expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
+ expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
+ expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
end
it 'returns a Branch with UTF-8 fields' do
- branches = @repo.local_branches.to_a
+ branches = repository.local_branches.to_a
expect(branches.size).to be > 0
branches.each do |branch|
expect(branch.name).to be_utf8
@@ -1413,11 +1267,11 @@ describe Gitlab::Git::Repository, :seed_helper do
it 'gets the branches from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:local_branches)
.and_return([])
- @repo.local_branches
+ repository.local_branches
end
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :local_branches do
- subject { @repo.local_branches }
+ subject { repository.local_branches }
end
end
@@ -1471,56 +1325,9 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- describe '#with_repo_branch_commit' do
- context 'when comparing with the same repository' do
- let(:start_repository) { repository }
-
- context 'when the branch exists' do
- let(:start_branch_name) { 'master' }
-
- it 'yields the commit' do
- expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
- .to yield_with_args(an_instance_of(Gitlab::Git::Commit))
- end
- end
-
- context 'when the branch does not exist' do
- let(:start_branch_name) { 'definitely-not-master' }
-
- it 'yields nil' do
- expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
- .to yield_with_args(nil)
- end
- end
- end
-
- context 'when comparing with another repository' do
- let(:start_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
-
- context 'when the branch exists' do
- let(:start_branch_name) { 'master' }
-
- it 'yields the commit' do
- expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
- .to yield_with_args(an_instance_of(Gitlab::Git::Commit))
- end
- end
-
- context 'when the branch does not exist' do
- let(:start_branch_name) { 'definitely-not-master' }
-
- it 'yields nil' do
- expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
- .to yield_with_args(nil)
- end
- end
- end
- end
-
describe '#fetch_source_branch!' do
let(:local_ref) { 'refs/merge-requests/1/head' }
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
- let(:source_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+ let(:source_repository) { mutable_repository }
after do
ensure_seeds
@@ -1529,7 +1336,8 @@ describe Gitlab::Git::Repository, :seed_helper do
context 'when the branch exists' do
context 'when the commit does not exist locally' do
let(:source_branch) { 'new-branch-for-fetch-source-branch' }
- let(:source_rugged) { Gitlab::GitalyClient::StorageSettings.allow_disk_access { source_repository.rugged } }
+ let(:source_path) { File.join(TestEnv.repos_path, source_repository.relative_path) }
+ let(:source_rugged) { Rugged::Repository.new(source_path) }
let(:new_oid) { new_commit_edit_old_file(source_rugged).oid }
before do
@@ -1567,29 +1375,19 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#rm_branch' do
- shared_examples "user deleting a branch" do
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository.raw }
- let(:branch_name) { "to-be-deleted-soon" }
-
- before do
- project.add_developer(user)
- repository.create_branch(branch_name)
- end
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
+ let(:branch_name) { "to-be-deleted-soon" }
- it "removes the branch from the repo" do
- repository.rm_branch(branch_name, user: user)
-
- expect(repository_rugged.branches[branch_name]).to be_nil
- end
+ before do
+ project.add_developer(user)
+ repository.create_branch(branch_name)
end
- context "when Gitaly user_delete_branch is enabled" do
- it_behaves_like "user deleting a branch"
- end
+ it "removes the branch from the repo" do
+ repository.rm_branch(branch_name, user: user)
- context "when Gitaly user_delete_branch is disabled", :skip_gitaly_mock do
- it_behaves_like "user deleting a branch"
+ expect(repository_rugged.branches[branch_name]).to be_nil
end
end
@@ -1651,8 +1449,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#set_config' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- let(:rugged) { repository_rugged }
+ let(:repository) { mutable_repository }
let(:entries) do
{
'test.foo1' => 'bla bla',
@@ -1664,19 +1461,18 @@ describe Gitlab::Git::Repository, :seed_helper do
it 'can set config settings' do
expect(repository.set_config(entries)).to be_nil
- expect(rugged.config['test.foo1']).to eq('bla bla')
- expect(rugged.config['test.foo2']).to eq('1234')
- expect(rugged.config['test.foo3']).to eq('true')
+ expect(repository_rugged.config['test.foo1']).to eq('bla bla')
+ expect(repository_rugged.config['test.foo2']).to eq('1234')
+ expect(repository_rugged.config['test.foo3']).to eq('true')
end
after do
- entries.keys.each { |k| rugged.config.delete(k) }
+ entries.keys.each { |k| repository_rugged.config.delete(k) }
end
end
describe '#delete_config' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- let(:rugged) { repository_rugged }
+ let(:repository) { mutable_repository }
let(:entries) do
{
'test.foo1' => 'bla bla',
@@ -1687,21 +1483,19 @@ describe Gitlab::Git::Repository, :seed_helper do
it 'can delete config settings' do
entries.each do |key, value|
- rugged.config[key] = value
+ repository_rugged.config[key] = value
end
expect(repository.delete_config(*%w[does.not.exist test.foo1 test.foo2])).to be_nil
- config_keys = rugged.config.each_key.to_a
+ config_keys = repository_rugged.config.each_key.to_a
expect(config_keys).not_to include('test.foo1')
expect(config_keys).not_to include('test.foo2')
end
end
describe '#merge' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
let(:target_branch) { 'test-merge-target-branch' }
@@ -1713,46 +1507,34 @@ describe Gitlab::Git::Repository, :seed_helper do
ensure_seeds
end
- shared_examples '#merge' do
- it 'can perform a merge' do
- merge_commit_id = nil
- result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id|
- merge_commit_id = commit_id
- end
-
- expect(result.newrev).to eq(merge_commit_id)
- expect(result.repo_created).to eq(false)
- expect(result.branch_created).to eq(false)
+ it 'can perform a merge' do
+ merge_commit_id = nil
+ result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id|
+ merge_commit_id = commit_id
end
- it 'returns nil if there was a concurrent branch update' do
- concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
- result = repository.merge(user, source_sha, target_branch, 'Test merge') do
- # This ref update should make the merge fail
- repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id)
- end
-
- # This 'nil' signals that the merge was not applied
- expect(result).to be_nil
+ expect(result.newrev).to eq(merge_commit_id)
+ expect(result.repo_created).to eq(false)
+ expect(result.branch_created).to eq(false)
+ end
- # Our concurrent ref update should not have been undone
- expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id)
+ it 'returns nil if there was a concurrent branch update' do
+ concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
+ result = repository.merge(user, source_sha, target_branch, 'Test merge') do
+ # This ref update should make the merge fail
+ repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id)
end
- end
- context 'with gitaly' do
- it_behaves_like '#merge'
- end
+ # This 'nil' signals that the merge was not applied
+ expect(result).to be_nil
- context 'without gitaly', :skip_gitaly_mock do
- it_behaves_like '#merge'
+ # Our concurrent ref update should not have been undone
+ expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id)
end
end
describe '#ff_merge' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
let(:target_branch) { 'test-ff-target-branch' }
@@ -1815,9 +1597,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#delete_all_refs_except' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ let(:repository) { mutable_repository }
before do
repository.write_ref("refs/delete/a", "0b4bc9a49b562e85de7cc9e834518ea6828729b9")
@@ -1841,12 +1621,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe 'remotes' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
- let(:rugged) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.rugged }
- end
+ let(:repository) { mutable_repository }
let(:remote_name) { 'my-remote' }
let(:url) { 'http://my-repo.git' }
@@ -1857,88 +1632,47 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '#add_remote' do
let(:mirror_refmap) { '+refs/*:refs/*' }
- shared_examples 'add_remote' do
- it 'added the remote' do
- begin
- rugged.remotes.delete(remote_name)
- rescue Rugged::ConfigError
- end
-
- repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap)
-
- expect(rugged.remotes[remote_name]).not_to be_nil
- expect(rugged.config["remote.#{remote_name}.mirror"]).to eq('true')
- expect(rugged.config["remote.#{remote_name}.prune"]).to eq('true')
- expect(rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap)
+ it 'added the remote' do
+ begin
+ repository_rugged.remotes.delete(remote_name)
+ rescue Rugged::ConfigError
end
- end
- context 'using Gitaly' do
- it_behaves_like 'add_remote'
- end
+ repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap)
- context 'with Gitaly disabled', :disable_gitaly do
- it_behaves_like 'add_remote'
+ expect(repository_rugged.remotes[remote_name]).not_to be_nil
+ expect(repository_rugged.config["remote.#{remote_name}.mirror"]).to eq('true')
+ expect(repository_rugged.config["remote.#{remote_name}.prune"]).to eq('true')
+ expect(repository_rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap)
end
end
describe '#remove_remote' do
- shared_examples 'remove_remote' do
- it 'removes the remote' do
- rugged.remotes.create(remote_name, url)
+ it 'removes the remote' do
+ repository_rugged.remotes.create(remote_name, url)
- repository.remove_remote(remote_name)
+ repository.remove_remote(remote_name)
- expect(rugged.remotes[remote_name]).to be_nil
- end
- end
-
- context 'using Gitaly' do
- it_behaves_like 'remove_remote'
- end
-
- context 'with Gitaly disabled', :disable_gitaly do
- it_behaves_like 'remove_remote'
+ expect(repository_rugged.remotes[remote_name]).to be_nil
end
end
end
- describe '#gitlab_projects' do
- subject { repository.gitlab_projects }
-
- it do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- expect(subject.shard_path).to eq(storage_path)
- end
- end
- it { expect(subject.repository_relative_path).to eq(repository.relative_path) }
- end
-
describe '#bundle_to_disk' do
- shared_examples 'bundling to disk' do
- let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
-
- after do
- FileUtils.rm_rf(save_path)
- end
-
- it 'saves a bundle to disk' do
- repository.bundle_to_disk(save_path)
+ let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
- success = system(
- *%W(#{Gitlab.config.git.bin_path} -C #{repository_path} bundle verify #{save_path}),
- [:out, :err] => '/dev/null'
- )
- expect(success).to be true
- end
+ after do
+ FileUtils.rm_rf(save_path)
end
- context 'when Gitaly bundle_to_disk feature is enabled' do
- it_behaves_like 'bundling to disk'
- end
+ it 'saves a bundle to disk' do
+ repository.bundle_to_disk(save_path)
- context 'when Gitaly bundle_to_disk feature is disabled', :disable_gitaly do
- it_behaves_like 'bundling to disk'
+ success = system(
+ *%W(#{Gitlab.config.git.bin_path} -C #{repository_path} bundle verify #{save_path}),
+ [:out, :err] => '/dev/null'
+ )
+ expect(success).to be true
end
end
@@ -1975,7 +1709,7 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '#checksum' do
it 'calculates the checksum for non-empty repo' do
- expect(repository.checksum).to eq '4be7d24ce7e8d845502d599b72d567d23e6a40c0'
+ expect(repository.checksum).to eq '51d0a9662681f93e1fee547a6b7ba2bcaf716059'
end
it 'returns 0000000000000000000000000000000000000000 for an empty repo' do
@@ -2013,138 +1747,41 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- context 'gitlab_projects commands' do
- let(:gitlab_projects) { repository.gitlab_projects }
- let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
-
- describe '#push_remote_branches' do
- subject do
- repository.push_remote_branches('downstream-remote', ['master'])
- end
-
- it 'executes the command' do
- expect(gitlab_projects).to receive(:push_branches)
- .with('downstream-remote', timeout, true, ['master'])
- .and_return(true)
-
- is_expected.to be_truthy
- end
-
- it 'raises an error if the command fails' do
- allow(gitlab_projects).to receive(:output) { 'error' }
- expect(gitlab_projects).to receive(:push_branches)
- .with('downstream-remote', timeout, true, ['master'])
- .and_return(false)
-
- expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
- end
- end
-
- describe '#delete_remote_branches' do
- subject do
- repository.delete_remote_branches('downstream-remote', ['master'])
- end
-
- it 'executes the command' do
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(true)
-
- is_expected.to be_truthy
- end
-
- it 'raises an error if the command fails' do
- allow(gitlab_projects).to receive(:output) { 'error' }
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(false)
-
- expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
- end
- end
+ describe '#clean_stale_repository_files' do
+ let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') }
- describe '#delete_remote_branches' do
- subject do
- repository.delete_remote_branches('downstream-remote', ['master'])
- end
+ it 'cleans up the files' do
+ create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master]
+ raise 'preparation failed' unless system(*create_worktree, err: '/dev/null')
- it 'executes the command' do
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(true)
+ FileUtils.touch(worktree_path, mtime: Time.now - 8.hours)
+ # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
+ # but the HEAD must be 40 characters long or git will ignore it.
+ File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
- is_expected.to be_truthy
- end
+ # git 2.16 fails with "fatal: bad object HEAD"
+ expect(rev_list_all).to be false
- it 'raises an error if the command fails' do
- allow(gitlab_projects).to receive(:output) { 'error' }
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(false)
+ repository.clean_stale_repository_files
- expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
- end
+ expect(rev_list_all).to be true
+ expect(File.exist?(worktree_path)).to be_falsey
end
- describe '#clean_stale_repository_files' do
- let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') }
-
- it 'cleans up the files' do
- create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master]
- raise 'preparation failed' unless system(*create_worktree, err: '/dev/null')
-
- FileUtils.touch(worktree_path, mtime: Time.now - 8.hours)
- # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
- # but the HEAD must be 40 characters long or git will ignore it.
- File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
-
- # git 2.16 fails with "fatal: bad object HEAD"
- expect(rev_list_all).to be false
-
- repository.clean_stale_repository_files
-
- expect(rev_list_all).to be true
- expect(File.exist?(worktree_path)).to be_falsey
- end
-
- def rev_list_all
- system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null')
- end
-
- it 'increments a counter upon an error' do
- expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError)
-
- counter = double(:counter)
-
- expect(counter).to receive(:increment)
- expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total,
- 'Number of failed repository cleanup events').and_return(counter)
-
- repository.clean_stale_repository_files
- end
+ def rev_list_all
+ system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null')
end
- describe '#delete_remote_branches' do
- subject do
- repository.delete_remote_branches('downstream-remote', ['master'])
- end
+ it 'increments a counter upon an error' do
+ expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError)
- it 'executes the command' do
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(true)
+ counter = double(:counter)
- is_expected.to be_truthy
- end
+ expect(counter).to receive(:increment)
+ expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total,
+ 'Number of failed repository cleanup events').and_return(counter)
- it 'raises an error if the command fails' do
- allow(gitlab_projects).to receive(:output) { 'error' }
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(false)
-
- expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
- end
+ repository.clean_stale_repository_files
end
end
@@ -2187,13 +1824,11 @@ describe Gitlab::Git::Repository, :seed_helper do
end
context 'when the diff contains a rename' do
- let(:repo) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged }
- let(:end_sha) { new_commit_move_file(repo).oid }
+ let(:end_sha) { new_commit_move_file(repository_rugged).oid }
after do
# Erase our commits so other tests get the original repo
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- repo.references.update('refs/heads/master', SeedRepo::LastCommit::ID)
+ repository_rugged.references.update('refs/heads/master', SeedRepo::LastCommit::ID)
end
it 'does not include the renamed file in the sparse checkout' do
@@ -2240,10 +1875,9 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- def create_remote_branch(repository, remote_name, branch_name, source_branch_name)
+ def create_remote_branch(remote_name, branch_name, source_branch_name)
source_branch = repository.branches.find { |branch| branch.name == source_branch_name }
- rugged = repository_rugged
- rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha)
+ repository_rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha)
end
# Build the options hash that's passed to Rugged::Commit#create
@@ -2321,16 +1955,4 @@ describe Gitlab::Git::Repository, :seed_helper do
line.split("\t").last
end
end
-
- def repository_rugged
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged
- end
- end
-
- def repository_path
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.path
- end
- end
end
diff --git a/spec/lib/gitlab/git/user_spec.rb b/spec/lib/gitlab/git/user_spec.rb
index 99d850e1df9..d9d338206f8 100644
--- a/spec/lib/gitlab/git/user_spec.rb
+++ b/spec/lib/gitlab/git/user_spec.rb
@@ -22,10 +22,19 @@ describe Gitlab::Git::User do
end
describe '.from_gitlab' do
- let(:user) { build(:user) }
- subject { described_class.from_gitlab(user) }
+ context 'when no commit_email has been set' do
+ let(:user) { build(:user, email: 'alice@example.com', commit_email: nil) }
+ subject { described_class.from_gitlab(user) }
- it { expect(subject).to eq(described_class.new(user.username, user.name, user.email, 'user-')) }
+ it { expect(subject).to eq(described_class.new(user.username, user.name, user.email, 'user-')) }
+ end
+
+ context 'when commit_email has been set' do
+ let(:user) { build(:user, email: 'alice@example.com', commit_email: 'bob@example.com') }
+ subject { described_class.from_gitlab(user) }
+
+ it { expect(subject).to eq(described_class.new(user.username, user.name, user.commit_email, 'user-')) }
+ end
end
describe '#==' do
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index dbd64c4bec0..e7da5565c26 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::GitAccess do
include TermsHelper
+ include GitHelpers
let(:user) { create(:user) }
@@ -736,21 +737,19 @@ describe Gitlab::GitAccess do
def merge_into_protected_branch
@protected_branch_merge_commit ||= begin
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.add_branch(user, unprotected_branch, 'feature')
- rugged = project.repository.rugged
- target_branch = rugged.rev_parse('feature')
- source_branch = project.repository.create_file(
- user,
- 'filename',
- 'This is the file content',
- message: 'This is a good commit message',
- branch_name: unprotected_branch)
- author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
-
- merge_index = rugged.merge_commits(target_branch, source_branch)
- Rugged::Commit.create(rugged, author: author, committer: author, message: "commit message", parents: [target_branch, source_branch], tree: merge_index.write_tree(rugged))
- end
+ project.repository.add_branch(user, unprotected_branch, 'feature')
+ rugged = rugged_repo(project.repository)
+ target_branch = rugged.rev_parse('feature')
+ source_branch = project.repository.create_file(
+ user,
+ 'filename',
+ 'This is the file content',
+ message: 'This is a good commit message',
+ branch_name: unprotected_branch)
+ author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
+
+ merge_index = rugged.merge_commits(target_branch, source_branch)
+ Rugged::Commit.create(rugged, author: author, committer: author, message: "commit message", parents: [target_branch, source_branch], tree: merge_index.write_tree(rugged))
end
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 54f2ea33f90..d7bd757149d 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -19,7 +19,14 @@ describe Gitlab::GitalyClient::CommitService do
right_commit_id: commit.id,
collapse_diffs: false,
enforce_limits: true,
- **Gitlab::Git::DiffCollection.collection_limits.to_h
+ # Tests limitation parameters explicitly
+ max_files: 100,
+ max_lines: 5000,
+ max_bytes: 512000,
+ safe_max_files: 100,
+ safe_max_lines: 5000,
+ safe_max_bytes: 512000,
+ max_patch_bytes: 102400
)
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
@@ -37,7 +44,14 @@ describe Gitlab::GitalyClient::CommitService do
right_commit_id: initial_commit.id,
collapse_diffs: false,
enforce_limits: true,
- **Gitlab::Git::DiffCollection.collection_limits.to_h
+ # Tests limitation parameters explicitly
+ max_files: 100,
+ max_lines: 5000,
+ max_bytes: 512000,
+ safe_max_files: 100,
+ safe_max_lines: 5000,
+ safe_max_bytes: 512000,
+ max_patch_bytes: 102400
)
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
@@ -104,6 +118,22 @@ describe Gitlab::GitalyClient::CommitService do
end
end
+ describe '#diff_stats' do
+ let(:left_commit_id) { 'master' }
+ let(:right_commit_id) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
+
+ it 'sends an RPC request' do
+ request = Gitaly::DiffStatsRequest.new(repository: repository_message,
+ left_commit_id: left_commit_id,
+ right_commit_id: right_commit_id)
+
+ expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:diff_stats)
+ .with(request, kind_of(Hash)).and_return([])
+
+ described_class.new(repository).diff_stats(left_commit_id, right_commit_id)
+ end
+ end
+
describe '#tree_entries' do
let(:path) { '/' }
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index f03c7e3f04b..9030a49983d 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -45,6 +45,26 @@ describe Gitlab::GitalyClient::RemoteService do
end
end
+ describe '#find_remote_root_ref' do
+ it 'sends an find_remote_root_ref message and returns the root ref' do
+ expect_any_instance_of(Gitaly::RemoteService::Stub)
+ .to receive(:find_remote_root_ref)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(ref: 'master'))
+
+ expect(client.find_remote_root_ref('origin')).to eq 'master'
+ end
+
+ it 'ensure ref is a valid UTF-8 string' do
+ expect_any_instance_of(Gitaly::RemoteService::Stub)
+ .to receive(:find_remote_root_ref)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(ref: "an_invalid_ref_\xE5"))
+
+ expect(client.find_remote_root_ref('origin')).to eq "an_invalid_ref_Ã¥"
+ end
+ end
+
describe '#update_remote_mirror' do
let(:ref_name) { 'remote_mirror_1' }
let(:only_branches_matching) { ['my-branch', 'master'] }
diff --git a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
index 5f67fe6b952..d82c9c28da0 100644
--- a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
@@ -39,6 +39,10 @@ describe Gitlab::GitalyClient::WikiService do
expect(wiki_page.title).to eq('My Page')
expect(wiki_page.raw_data).to eq('ab')
expect(wiki_page_version.format).to eq('markdown')
+
+ expect(wiki_page.title).to be_utf8
+ expect(wiki_page.path).to be_utf8
+ expect(wiki_page.name).to be_utf8
end
end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 29e61d15726..88f7099ff3c 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -56,5 +56,22 @@ describe Gitlab::Highlight do
described_class.highlight('file.name', 'Contents')
end
+
+ context 'timeout' do
+ subject { described_class.new('file.name', 'Contents') }
+
+ it 'utilizes timeout for web' do
+ expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_FOREGROUND).and_call_original
+
+ subject.highlight("Content")
+ end
+
+ it 'utilizes longer timeout for sidekiq' do
+ allow(Sidekiq).to receive(:server?).and_return(true)
+ expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_BACKGROUND).and_call_original
+
+ subject.highlight("Content")
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb
deleted file mode 100644
index 5059d68e54b..00000000000
--- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
- let!(:service) { described_class.new }
- let!(:project) { create(:project, :with_object_export) }
- let(:shared) { project.import_export_shared }
- let!(:user) { create(:user) }
-
- describe '#execute' do
- before do
- allow(service).to receive(:strategy_execute)
- stub_feature_flags(import_export_object_storage: true)
- end
-
- it 'returns if project exported file is not found' do
- allow(project).to receive(:export_project_object_exists?).and_return(false)
-
- 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/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
index 566b7f46c87..9a442de2900 100644
--- 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
@@ -9,11 +9,10 @@ describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
describe '#execute' do
before do
allow(service).to receive(:strategy_execute)
- stub_feature_flags(import_export_object_storage: false)
end
it 'returns if project exported file is not found' do
- allow(project).to receive(:export_project_path).and_return(nil)
+ allow(project).to receive(:export_file_exists?).and_return(false)
expect(service).not_to receive(:strategy_execute)
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
index 7f2e0a4ee2c..ec17ad8541f 100644
--- 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
@@ -24,34 +24,13 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
end
describe '#execute' do
- context 'without object storage' do
- before do
- stub_feature_flags(import_export_object_storage: false)
- end
-
- 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
-
- context 'with object storage' do
- before do
- stub_feature_flags(import_export_object_storage: true)
- end
+ it 'removes the exported project file after the upload' do
+ allow(strategy).to receive(:send_file)
+ allow(strategy).to receive(:handle_response_error)
- 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_exports)
- expect(project).to receive(:remove_exported_project_file)
-
- strategy.execute(user, project)
- end
+ strategy.execute(user, project)
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index b4269bd5786..fe167033941 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -117,6 +117,7 @@ pipelines:
- retryable_builds
- cancelable_statuses
- manual_actions
+- scheduled_actions
- artifacts
- pipeline_schedule
- merge_requests
@@ -288,6 +289,7 @@ project:
- fork_network_member
- fork_network
- custom_attributes
+- prometheus_metrics
- lfs_file_locks
- project_badges
- source_of_merge_requests
@@ -303,6 +305,8 @@ award_emoji:
- user
priorities:
- label
+prometheus_metrics:
+- project
timelogs:
- issue
- merge_request
@@ -321,3 +325,9 @@ metrics:
- latest_closed_by
- merged_by
- pipeline
+resource_label_events:
+- user
+- issue
+- merge_request
+- epic
+- label
diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
index 4897d604bc1..e44ff6bbcbd 100644
--- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
@@ -6,22 +6,35 @@ describe Gitlab::ImportExport::AvatarRestorer do
let(:shared) { project.import_export_shared }
let(:project) { create(:project) }
- before do
- allow_any_instance_of(described_class).to receive(:avatar_export_file)
- .and_return(uploaded_image_temp_path)
- end
-
after do
project.remove_avatar!
end
- it 'restores a project avatar' do
- expect(described_class.new(project: project, shared: shared).restore).to be true
+ context 'with avatar' do
+ before do
+ allow_any_instance_of(described_class).to receive(:avatar_export_file)
+ .and_return(uploaded_image_temp_path)
+ end
+
+ it 'restores a project avatar' do
+ expect(described_class.new(project: project, shared: shared).restore).to be true
+ end
+
+ it 'saves the avatar into the project' do
+ described_class.new(project: project, shared: shared).restore
+
+ expect(project.reload.avatar.file.exists?).to be true
+ end
end
- it 'saves the avatar into the project' do
- described_class.new(project: project, shared: shared).restore
+ it 'does not break if there is just a directory' do
+ Dir.mktmpdir do |tmpdir|
+ FileUtils.mkdir_p("#{tmpdir}/a/b")
+
+ allow_any_instance_of(described_class).to receive(:avatar_export_path)
+ .and_return("#{tmpdir}/a")
- expect(project.reload.avatar.file.exists?).to be true
+ expect(described_class.new(project: project, shared: shared).restore).to be true
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
index 90e6d653d34..2bd1b9924c6 100644
--- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -8,8 +8,7 @@ describe Gitlab::ImportExport::AvatarSaver do
before do
FileUtils.mkdir_p("#{shared.export_path}/avatar/")
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
- stub_feature_flags(import_export_object_storage: false)
+ allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:export_path).and_return(export_path)
end
after do
@@ -19,7 +18,7 @@ describe Gitlab::ImportExport::AvatarSaver do
it 'saves a project avatar' do
described_class.new(project: project_with_avatar, shared: shared).save
- expect(File).to exist("#{shared.export_path}/avatar/dk.png")
+ expect(File).to exist(Dir["#{shared.export_path}/avatar/**/dk.png"].first)
end
it 'is fine not to have an avatar' do
diff --git a/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb b/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb
deleted file mode 100644
index 287745eb40e..00000000000
--- a/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::ImportExport::FileImporter do
- let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
- let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" }
- let(:valid_file) { "#{shared.export_path}/valid.json" }
- let(:symlink_file) { "#{shared.export_path}/invalid.json" }
- let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" }
- let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
- let(:evil_symlink_file) { "#{shared.export_path}/.\nevil" }
-
- before do
- stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(FileUploader)
-
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(storage_path)
- allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
- allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test')
- allow(SecureRandom).to receive(:hex).and_return('abcd')
- setup_files
- end
-
- after do
- FileUtils.rm_rf(storage_path)
- end
-
- context 'normal run' do
- before do
- described_class.import(project: build(:project), archive_file: '', shared: shared)
- end
-
- it 'removes symlinks in root folder' do
- expect(File.exist?(symlink_file)).to be false
- end
-
- it 'removes hidden symlinks in root folder' do
- expect(File.exist?(hidden_symlink_file)).to be false
- end
-
- it 'removes evil symlinks in root folder' do
- expect(File.exist?(evil_symlink_file)).to be false
- end
-
- it 'removes symlinks in subfolders' do
- expect(File.exist?(subfolder_symlink_file)).to be false
- end
-
- it 'does not remove a valid file' do
- expect(File.exist?(valid_file)).to be true
- end
-
- it 'creates the file in the right subfolder' do
- expect(shared.export_path).to include('test/abcd')
- end
- end
-
- context 'error' do
- before do
- allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError)
- described_class.import(project: build(:project), archive_file: '', shared: shared)
- end
-
- it 'removes symlinks in root folder' do
- expect(File.exist?(symlink_file)).to be false
- end
-
- it 'removes hidden symlinks in root folder' do
- expect(File.exist?(hidden_symlink_file)).to be false
- end
-
- it 'removes symlinks in subfolders' do
- expect(File.exist?(subfolder_symlink_file)).to be false
- end
-
- it 'does not remove a valid file' do
- expect(File.exist?(valid_file)).to be true
- end
- end
-
- def setup_files
- FileUtils.mkdir_p("#{shared.export_path}/subfolder/")
- FileUtils.touch(valid_file)
- FileUtils.ln_s(valid_file, symlink_file)
- FileUtils.ln_s(valid_file, subfolder_symlink_file)
- FileUtils.ln_s(valid_file, hidden_symlink_file)
- FileUtils.ln_s(valid_file, evil_symlink_file)
- end
-end
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 78fccdf1dfc..bf34cefe18f 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::FileImporter do
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
- let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" }
+ let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
let(:symlink_file) { "#{shared.export_path}/invalid.json" }
let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" }
@@ -11,7 +11,9 @@ describe Gitlab::ImportExport::FileImporter do
before do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+ stub_uploads_object_storage(FileUploader)
+
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(storage_path)
allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test')
allow(SecureRandom).to receive(:hex).and_return('abcd')
@@ -19,12 +21,12 @@ describe Gitlab::ImportExport::FileImporter do
end
after do
- FileUtils.rm_rf(export_path)
+ FileUtils.rm_rf(storage_path)
end
context 'normal run' do
before do
- described_class.import(project: nil, archive_file: '', shared: shared)
+ described_class.import(project: build(:project), archive_file: '', shared: shared)
end
it 'removes symlinks in root folder' do
@@ -55,7 +57,7 @@ describe Gitlab::ImportExport::FileImporter do
context 'error' do
before do
allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError)
- described_class.import(project: nil, archive_file: '', shared: shared)
+ described_class.import(project: build(:project), archive_file: '', shared: shared)
end
it 'removes symlinks in root folder' do
diff --git a/spec/lib/gitlab/import_export/importer_object_storage_spec.rb b/spec/lib/gitlab/import_export/importer_object_storage_spec.rb
deleted file mode 100644
index 24a994b3611..00000000000
--- a/spec/lib/gitlab/import_export/importer_object_storage_spec.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::ImportExport::Importer do
- let(:user) { create(:user) }
- let(:test_path) { "#{Dir.tmpdir}/importer_spec" }
- let(:shared) { project.import_export_shared }
- let(:project) { create(:project) }
- let(:import_file) { fixture_file_upload('spec/features/projects/import_export/test_project_export.tar.gz') }
-
- subject(:importer) { described_class.new(project) }
-
- before do
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path)
- allow_any_instance_of(Gitlab::ImportExport::FileImporter).to receive(:remove_import_file)
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(FileUploader)
-
- FileUtils.mkdir_p(shared.export_path)
- ImportExportUpload.create(project: project, import_file: import_file)
- end
-
- after do
- FileUtils.rm_rf(test_path)
- end
-
- describe '#execute' do
- it 'succeeds' do
- importer.execute
-
- expect(shared.errors).to be_empty
- end
-
- it 'extracts the archive' do
- expect(Gitlab::ImportExport::FileImporter).to receive(:import).and_call_original
-
- importer.execute
- end
-
- it 'checks the version' do
- expect(Gitlab::ImportExport::VersionChecker).to receive(:check!).and_call_original
-
- importer.execute
- end
-
- context 'all restores are executed' do
- [
- Gitlab::ImportExport::AvatarRestorer,
- Gitlab::ImportExport::RepoRestorer,
- Gitlab::ImportExport::WikiRestorer,
- Gitlab::ImportExport::UploadsRestorer,
- Gitlab::ImportExport::LfsRestorer,
- Gitlab::ImportExport::StatisticsRestorer
- ].each do |restorer|
- it "calls the #{restorer}" do
- fake_restorer = double(restorer.to_s)
-
- expect(fake_restorer).to receive(:restore).and_return(true).at_least(1)
- expect(restorer).to receive(:new).and_return(fake_restorer).at_least(1)
-
- importer.execute
- end
- end
-
- it 'restores the ProjectTree' do
- expect(Gitlab::ImportExport::ProjectTreeRestorer).to receive(:new).and_call_original
-
- importer.execute
- end
-
- it 'removes the import file' do
- expect(importer).to receive(:remove_import_file).and_call_original
-
- importer.execute
-
- expect(project.import_export_upload.import_file&.file).to be_nil
- end
- end
-
- context 'when project successfully restored' do
- let!(:existing_project) { create(:project, namespace: user.namespace) }
- let(:project) { create(:project, namespace: user.namespace, name: 'whatever', path: 'whatever') }
-
- before do
- restorers = double(:restorers, all?: true)
-
- allow(subject).to receive(:import_file).and_return(true)
- allow(subject).to receive(:check_version!).and_return(true)
- allow(subject).to receive(:restorers).and_return(restorers)
- allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path }))
- end
-
- context 'when import_data' do
- context 'has original_path' do
- it 'overwrites existing project' do
- expect_any_instance_of(::Projects::OverwriteProjectService).to receive(:execute).with(existing_project)
-
- subject.execute
- end
- end
-
- context 'has not original_path' do
- before do
- allow(project).to receive(:import_data).and_return(double(data: {}))
- end
-
- it 'does not call the overwrite service' do
- expect_any_instance_of(::Projects::OverwriteProjectService).not_to receive(:execute).with(existing_project)
-
- subject.execute
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 8053c48ad6c..11f98d782b1 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -4,16 +4,18 @@ describe Gitlab::ImportExport::Importer do
let(:user) { create(:user) }
let(:test_path) { "#{Dir.tmpdir}/importer_spec" }
let(:shared) { project.import_export_shared }
- let(:project) { create(:project, import_source: File.join(test_path, 'test_project_export.tar.gz')) }
+ let(:project) { create(:project) }
+ let(:import_file) { fixture_file_upload('spec/features/projects/import_export/test_project_export.tar.gz') }
subject(:importer) { described_class.new(project) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path)
allow_any_instance_of(Gitlab::ImportExport::FileImporter).to receive(:remove_import_file)
+ stub_uploads_object_storage(FileUploader)
FileUtils.mkdir_p(shared.export_path)
- FileUtils.cp(Rails.root.join('spec/features/projects/import_export/test_project_export.tar.gz'), test_path)
+ ImportExportUpload.create(project: project, import_file: import_file)
end
after do
@@ -64,6 +66,14 @@ describe Gitlab::ImportExport::Importer do
importer.execute
end
+ it 'removes the import file' do
+ expect(importer).to receive(:remove_import_file).and_call_original
+
+ importer.execute
+
+ expect(project.import_export_upload.import_file&.file).to be_nil
+ end
+
it 'sets the correct visibility_level when visibility level is a string' do
project.create_or_update_import_data(
data: { override_params: { visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s } }
@@ -85,7 +95,6 @@ describe Gitlab::ImportExport::Importer do
allow(subject).to receive(:import_file).and_return(true)
allow(subject).to receive(:check_version!).and_return(true)
allow(subject).to receive(:restorers).and_return(restorers)
- allow(restorers).to receive(:all?).and_return(true)
allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path }))
end
diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb
index 5cb8f2589c8..2e28f978c3a 100644
--- a/spec/lib/gitlab/import_export/model_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb
@@ -16,14 +16,30 @@ describe 'Import/Export model configuration' do
# - User, Author... Models we do not care about for checking models
names.flatten.uniq - %w(milestones labels user author) + ['project']
end
+ let(:ce_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' }
+ let(:ce_models_hash) { YAML.load_file(ce_models_yml) }
+
+ let(:ee_models_yml) { 'ee/spec/lib/gitlab/import_export/all_models.yml' }
+ let(:ee_models_hash) { File.exist?(ee_models_yml) ? YAML.load_file(ee_models_yml) : {} }
- let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' }
- let(:all_models) { YAML.load_file(all_models_yml) }
let(:current_models) { setup_models }
+ let(:all_models_hash) do
+ all_models_hash = ce_models_hash.dup
+
+ all_models_hash.each do |model, associations|
+ associations.concat(ee_models_hash[model] || [])
+ end
+
+ ee_models_hash.each do |model, associations|
+ all_models_hash[model] ||= associations
+ end
+
+ all_models_hash
+ end
it 'has no new models' do
model_names.each do |model_name|
- new_models = Array(current_models[model_name]) - Array(all_models[model_name])
+ new_models = Array(current_models[model_name]) - Array(all_models_hash[model_name])
expect(new_models).to be_empty, failure_message(model_name.classify, new_models)
end
end
@@ -31,27 +47,21 @@ describe 'Import/Export model configuration' do
# List of current models between models, in the format of
# {model: [model_2, model3], ...}
def setup_models
- all_models_hash = {}
-
- model_names.each do |model_name|
- model_class = relation_class_for_name(model_name)
-
- all_models_hash[model_name] = associations_for(model_class) - ['project']
+ model_names.each_with_object({}) do |model_name, hash|
+ hash[model_name] = associations_for(relation_class_for_name(model_name)) - ['project']
end
-
- all_models_hash
end
def failure_message(parent_model_name, new_models)
- <<-MSG
+ <<~MSG
New model(s) <#{new_models.join(',')}> have been added, related to #{parent_model_name}, which is exported by
the Import/Export feature.
- If you think this model should be included in the export, please add it to IMPORT_EXPORT_CONFIG.
- Definitely add it to MODELS_JSON to signal that you've handled this error and to prevent it from showing up in the future.
+ If you think this model should be included in the export, please add it to `#{Gitlab::ImportExport.config_file}`.
- MODELS_JSON: #{File.expand_path(all_models_yml)}
- IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+ Definitely add it to `#{File.expand_path(ce_models_yml)}`
+ #{"or `#{File.expand_path(ee_models_yml)}` if the model/associations are EE-specific\n" if ee_models_hash.any?}
+ to signal that you've handled this error and to prevent it from showing up in the future.
MSG
end
end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 1b7fa11cb3c..3f2281f213f 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -331,6 +331,28 @@
},
"events": []
}
+ ],
+ "resource_label_events": [
+ {
+ "id":244,
+ "action":"remove",
+ "issue_id":40,
+ "merge_request_id":null,
+ "label_id":2,
+ "user_id":1,
+ "created_at":"2018-08-28T08:24:00.494Z",
+ "label": {
+ "id": 2,
+ "title": "test2",
+ "color": "#428bca",
+ "project_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "type": "ProjectLabel"
+ }
+ }
]
},
{
@@ -2515,6 +2537,17 @@
"events": []
}
],
+ "resource_label_events": [
+ {
+ "id":243,
+ "action":"add",
+ "issue_id":null,
+ "merge_request_id":27,
+ "label_id":null,
+ "user_id":1,
+ "created_at":"2018-08-28T08:24:00.494Z"
+ }
+ ],
"merge_request_diff": {
"id": 27,
"state": "collected",
@@ -6110,7 +6143,7 @@
"id": 36,
"project_id": 5,
"ref": "master",
- "sha": "be93687618e4b132087f430a4d8fc3a609c9b77c",
+ "sha": "sha-notes",
"before_sha": null,
"push_data": null,
"created_at": "2016-03-22T15:20:35.755Z",
@@ -6121,6 +6154,7 @@
"status": "failed",
"started_at": null,
"finished_at": null,
+ "user_id": 9999,
"duration": null,
"notes": [
{
@@ -6320,6 +6354,7 @@
},
{
"id": 38,
+ "iid": 1,
"project_id": 5,
"ref": "master",
"sha": "5f923865dde3436854e9ceb9cdb7815618d4e849",
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index a88ac0a091e..7ebfc61f5e7 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -59,7 +59,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
it 'creates a valid pipeline note' do
- expect(Ci::Pipeline.first.notes).not_to be_empty
+ expect(Ci::Pipeline.find_by_sha('sha-notes').notes).not_to be_empty
+ end
+
+ it 'pipeline has the correct user ID' do
+ expect(Ci::Pipeline.find_by_sha('sha-notes').user_id).to eq(@user.id)
end
it 'restores pipelines with missing ref' do
@@ -89,6 +93,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(ProtectedTag.first.create_access_levels).not_to be_empty
end
+ it 'restores issue resource label events' do
+ expect(Issue.find_by(title: 'Voluptatem').resource_label_events).not_to be_empty
+ end
+
+ it 'restores merge requests resource label events' do
+ expect(MergeRequest.find_by(title: 'MR1').resource_label_events).not_to be_empty
+ end
+
context 'event at forth level of the tree' do
let(:event) { Event.where(action: 6).first }
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index fec8a2af9ab..5dc372263ad 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -169,6 +169,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(priorities.flatten).not_to be_empty
end
+ it 'has issue resource label events' do
+ expect(saved_project_json['issues'].first['resource_label_events']).not_to be_empty
+ end
+
+ it 'has merge request resource label events' do
+ expect(saved_project_json['merge_requests'].first['resource_label_events']).not_to be_empty
+ end
+
it 'saves the correct service type' do
expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
end
@@ -291,6 +299,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
project: project,
commit_id: ci_build.pipeline.sha)
+ create(:resource_label_event, label: project_label, issue: issue)
+ create(:resource_label_event, label: group_label, merge_request: merge_request)
+
create(:event, :created, target: milestone, project: project, author: user)
create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' })
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index cf9e0f71910..a31f77484d8 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -191,9 +191,7 @@ describe Gitlab::ImportExport::RelationFactory do
"author" => {
"name" => "Administrator"
},
- "events" => [
-
- ]
+ "events" => []
}
end
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 7ffa84f906d..8a699eb1461 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::ImportExport::RepoRestorer do
+ include GitHelpers
+
describe 'bundle a project Git repo' do
let(:user) { create(:user) }
let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
@@ -36,9 +38,7 @@ describe Gitlab::ImportExport::RepoRestorer do
it 'has the webhooks' do
restorer.restore
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- expect(Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository)).to exist
- end
+ expect(project_hook_exists?(project)).to be true
end
end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 0a1e3eb83d3..f7935149b23 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -300,6 +300,7 @@ CommitStatus:
- retried
- protected
- failure_reason
+- scheduled_at
Ci::Variable:
- id
- project_id
@@ -416,6 +417,7 @@ ProjectHook:
- type
- service_id
- push_events
+- push_events_branch_filter
- issues_events
- merge_requests_events
- tag_push_events
@@ -491,6 +493,7 @@ ProjectFeature:
- snippets_access_level
- builds_access_level
- repository_access_level
+- pages_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:
@@ -554,6 +557,19 @@ ProjectCustomAttribute:
- project_id
- key
- value
+PrometheusMetric:
+- id
+- created_at
+- updated_at
+- project_id
+- y_label
+- unit
+- legend
+- title
+- query
+- group
+- common
+- identifier
Badge:
- id
- link_url
@@ -565,3 +581,11 @@ Badge:
- type
ProjectCiCdSetting:
- group_runners_enabled
+ResourceLabelEvent:
+- id
+- action
+- issue_id
+- merge_request_id
+- label_id
+- user_id
+- created_at
diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb
index 02f1a4b81aa..d185ff2dfcc 100644
--- a/spec/lib/gitlab/import_export/saver_spec.rb
+++ b/spec/lib/gitlab/import_export/saver_spec.rb
@@ -18,26 +18,12 @@ describe Gitlab::ImportExport::Saver do
FileUtils.rm_rf(export_path)
end
- context 'local archive' do
- it 'saves the repo to disk' do
- stub_feature_flags(import_export_object_storage: false)
+ it 'saves the repo using object storage' do
+ stub_uploads_object_storage(ImportExportUploader)
- subject.save
+ subject.save
- expect(shared.errors).to be_empty
- expect(Dir.empty?(shared.archive_path)).to be false
- end
- end
-
- context 'object storage' do
- it 'saves the repo using object storage' do
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(ImportExportUploader)
-
- subject.save
-
- expect(ImportExportUpload.find_by(project: project).export_file.url)
- .to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
- end
+ expect(ImportExportUpload.find_by(project: project).export_file.url)
+ .to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
end
end
diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
index f799de18cd0..792117e1df1 100644
--- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
@@ -4,6 +4,7 @@ describe Gitlab::ImportExport::UploadsManager do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project) { create(:project) }
+ let(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) }
let(:exported_file_path) { "#{shared.export_path}/uploads/#{upload.secret}/#{File.basename(upload.path)}" }
subject(:manager) { described_class.new(project: project, shared: shared) }
@@ -69,44 +70,20 @@ describe Gitlab::ImportExport::UploadsManager do
end
end
end
+ end
- context 'using object storage' do
- let!(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) }
-
- before do
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(FileUploader)
- end
-
- it 'saves the file' do
- fake_uri = double
-
- expect(fake_uri).to receive(:open).and_return(StringIO.new('File content'))
- expect(URI).to receive(:parse).and_return(fake_uri)
-
- manager.save
+ describe '#restore' do
+ before do
+ stub_uploads_object_storage(FileUploader)
- expect(File.read(exported_file_path)).to eq('File content')
- end
+ FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038'))
+ FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt"))
end
- describe '#restore' do
- context 'using object storage' do
- before do
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(FileUploader)
-
- FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038'))
- FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt"))
- end
+ it 'restores the file' do
+ manager.restore
- it 'restores the file' do
- manager.restore
-
- expect(project.uploads.size).to eq(1)
- expect(project.uploads.first.build_uploader.filename).to eq('dummy.txt')
- end
- end
+ expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt')
end
end
end
diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
index acef97459b8..6072f18b8c7 100644
--- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::ImportExport::UploadsRestorer do
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/random'))
- FileUtils.touch(File.join(shared.export_path, 'uploads/random', "dummy.txt"))
+ FileUtils.touch(File.join(shared.export_path, 'uploads/random', 'dummy.txt'))
end
after do
@@ -27,9 +27,7 @@ describe Gitlab::ImportExport::UploadsRestorer do
it 'copies the uploads to the project path' do
subject.restore
- uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) }
-
- expect(uploads).to include('dummy.txt')
+ expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt')
end
end
@@ -45,9 +43,7 @@ describe Gitlab::ImportExport::UploadsRestorer do
it 'copies the uploads to the project path' do
subject.restore
- uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) }
-
- expect(uploads).to include('dummy.txt')
+ expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt')
end
end
end
diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
index c716edd9397..24993460e51 100644
--- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
@@ -7,7 +7,6 @@ describe Gitlab::ImportExport::UploadsSaver do
let(:shared) { project.import_export_shared }
before do
- stub_feature_flags(import_export_object_storage: false)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
diff --git a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
new file mode 100644
index 00000000000..4a669408025
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::ClusterRoleBinding do
+ let(:cluster_role_binding) { described_class.new(name, cluster_role_name, subjects) }
+ let(:name) { 'cluster-role-binding-name' }
+ let(:cluster_role_name) { 'cluster-admin' }
+
+ let(:subjects) { [{ kind: 'ServiceAccount', name: 'sa', namespace: 'ns' }] }
+
+ describe '#generate' do
+ let(:role_ref) do
+ {
+ apiGroup: 'rbac.authorization.k8s.io',
+ kind: 'ClusterRole',
+ name: cluster_role_name
+ }
+ end
+
+ let(:resource) do
+ ::Kubeclient::Resource.new(
+ metadata: { name: name },
+ roleRef: role_ref,
+ subjects: subjects
+ )
+ end
+
+ subject { cluster_role_binding.generate }
+
+ it 'should build a Kubeclient Resource' do
+ is_expected.to eq(resource)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 341f71a3e49..9200724ed23 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -5,9 +5,18 @@ describe Gitlab::Kubernetes::Helm::Api do
let(:helm) { described_class.new(client) }
let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
let(:namespace) { Gitlab::Kubernetes::Namespace.new(gitlab_namespace, client) }
- let(:application) { create(:clusters_applications_prometheus) }
-
- let(:command) { application.install_command }
+ let(:application_name) { 'app-name' }
+ let(:rbac) { false }
+ let(:files) { {} }
+
+ let(:command) do
+ Gitlab::Kubernetes::Helm::InstallCommand.new(
+ name: application_name,
+ chart: 'chart-name',
+ rbac: rbac,
+ files: files
+ )
+ end
subject { helm }
@@ -28,6 +37,8 @@ describe Gitlab::Kubernetes::Helm::Api do
before do
allow(client).to receive(:create_pod).and_return(nil)
allow(client).to receive(:create_config_map).and_return(nil)
+ allow(client).to receive(:create_service_account).and_return(nil)
+ allow(client).to receive(:create_cluster_role_binding).and_return(nil)
allow(namespace).to receive(:ensure_exists!).once
end
@@ -39,7 +50,7 @@ describe Gitlab::Kubernetes::Helm::Api do
end
context 'with a ConfigMap' do
- let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application.name, application.files).generate }
+ let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application_name, files).generate }
it 'creates a ConfigMap on kubeclient' do
expect(client).to receive(:create_config_map).with(resource).once
@@ -47,6 +58,133 @@ describe Gitlab::Kubernetes::Helm::Api do
subject.install(command)
end
end
+
+ context 'without a service account' do
+ it 'does not create a service account on kubeclient' do
+ expect(client).not_to receive(:create_service_account)
+ expect(client).not_to receive(:create_cluster_role_binding)
+
+ subject.install(command)
+ end
+ end
+
+ context 'with a service account' do
+ let(:command) { Gitlab::Kubernetes::Helm::InitCommand.new(name: application_name, files: files, rbac: rbac) }
+
+ context 'rbac-enabled cluster' do
+ let(:rbac) { true }
+
+ let(:service_account_resource) do
+ Kubeclient::Resource.new(metadata: { name: 'tiller', namespace: 'gitlab-managed-apps' })
+ end
+
+ let(:cluster_role_binding_resource) do
+ Kubeclient::Resource.new(
+ metadata: { name: 'tiller-admin' },
+ roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'ClusterRole', name: 'cluster-admin' },
+ subjects: [{ kind: 'ServiceAccount', name: 'tiller', namespace: 'gitlab-managed-apps' }]
+ )
+ end
+
+ context 'service account and cluster role binding does not exist' do
+ before do
+ expect(client).to receive('get_service_account').with('tiller', 'gitlab-managed-apps').and_raise(Kubeclient::HttpError.new(404, 'Not found', nil))
+ expect(client).to receive('get_cluster_role_binding').with('tiller-admin').and_raise(Kubeclient::HttpError.new(404, 'Not found', nil))
+ end
+
+ it 'creates a service account, followed the cluster role binding on kubeclient' do
+ expect(client).to receive(:create_service_account).with(service_account_resource).once.ordered
+ expect(client).to receive(:create_cluster_role_binding).with(cluster_role_binding_resource).once.ordered
+
+ subject.install(command)
+ end
+ end
+
+ context 'service account already exists' do
+ before do
+ expect(client).to receive('get_service_account').with('tiller', 'gitlab-managed-apps').and_return(service_account_resource)
+ expect(client).to receive('get_cluster_role_binding').with('tiller-admin').and_raise(Kubeclient::HttpError.new(404, 'Not found', nil))
+ end
+
+ it 'updates the service account, followed by creating the cluster role binding' do
+ expect(client).to receive(:update_service_account).with(service_account_resource).once.ordered
+ expect(client).to receive(:create_cluster_role_binding).with(cluster_role_binding_resource).once.ordered
+
+ subject.install(command)
+ end
+ end
+
+ context 'service account and cluster role binding already exists' do
+ before do
+ expect(client).to receive('get_service_account').with('tiller', 'gitlab-managed-apps').and_return(service_account_resource)
+ expect(client).to receive('get_cluster_role_binding').with('tiller-admin').and_return(cluster_role_binding_resource)
+ end
+
+ it 'updates the service account, followed by creating the cluster role binding' do
+ expect(client).to receive(:update_service_account).with(service_account_resource).once.ordered
+ expect(client).to receive(:update_cluster_role_binding).with(cluster_role_binding_resource).once.ordered
+
+ subject.install(command)
+ end
+ end
+
+ context 'a non-404 error is thrown' do
+ before do
+ expect(client).to receive('get_service_account').with('tiller', 'gitlab-managed-apps').and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
+ end
+
+ it 'raises an error' do
+ expect { subject.install(command) }.to raise_error(Kubeclient::HttpError)
+ end
+ end
+ end
+
+ context 'legacy abac cluster' do
+ it 'does not create a service account on kubeclient' do
+ expect(client).not_to receive(:create_service_account)
+ expect(client).not_to receive(:create_cluster_role_binding)
+
+ subject.install(command)
+ end
+ end
+ end
+ end
+
+ describe '#update' do
+ let(:rbac) { false }
+
+ let(:command) do
+ Gitlab::Kubernetes::Helm::UpgradeCommand.new(
+ application_name,
+ chart: 'chart-name',
+ files: files,
+ rbac: rbac
+ )
+ end
+
+ before do
+ allow(namespace).to receive(:ensure_exists!).once
+
+ allow(client).to receive(:update_config_map).and_return(nil)
+ allow(client).to receive(:create_pod).and_return(nil)
+ end
+
+ it 'ensures the namespace exists before creating the pod' do
+ expect(namespace).to receive(:ensure_exists!).once.ordered
+ expect(client).to receive(:create_pod).once.ordered
+
+ subject.update(command)
+ end
+
+ it 'updates the config map on kubeclient when one exists' do
+ resource = Gitlab::Kubernetes::ConfigMap.new(
+ application_name, files
+ ).generate
+
+ expect(client).to receive(:update_config_map).with(resource).once
+
+ subject.update(command)
+ end
end
describe '#status' do
@@ -78,4 +216,25 @@ describe Gitlab::Kubernetes::Helm::Api do
subject.delete_pod!(command.pod_name)
end
end
+
+ describe '#get_config_map' do
+ before do
+ allow(namespace).to receive(:ensure_exists!).once
+ allow(client).to receive(:get_config_map).and_return(nil)
+ end
+
+ it 'ensures the namespace exists before retrieving the config map' do
+ expect(namespace).to receive(:ensure_exists!).once
+
+ subject.get_config_map('example-config-map-name')
+ end
+
+ it 'gets the config map on kubeclient' do
+ expect(client).to receive(:get_config_map)
+ .with('example-config-map-name', namespace.name)
+ .once
+
+ subject.get_config_map('example-config-map-name')
+ end
+ end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
index d50616e95e8..aacae78be43 100644
--- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
@@ -2,14 +2,24 @@ require 'spec_helper'
describe Gitlab::Kubernetes::Helm::BaseCommand do
let(:application) { create(:clusters_applications_helm) }
+ let(:rbac) { false }
+
let(:test_class) do
Class.new do
include Gitlab::Kubernetes::Helm::BaseCommand
+ def initialize(rbac)
+ @rbac = rbac
+ end
+
def name
"test-class-name"
end
+ def rbac?
+ @rbac
+ end
+
def files
{
some: 'value'
@@ -19,7 +29,7 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do
end
let(:base_command) do
- test_class.new
+ test_class.new(rbac)
end
subject { base_command }
@@ -34,6 +44,14 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do
it 'should returns a kubeclient resoure with pod content for application' do
is_expected.to be_an_instance_of ::Kubeclient::Resource
end
+
+ context 'when rbac is true' do
+ let(:rbac) { true }
+
+ it 'also returns a kubeclient resource' do
+ is_expected.to be_an_instance_of ::Kubeclient::Resource
+ end
+ end
end
describe '#pod_name' do
diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
index dcbc046cf00..72dc1817936 100644
--- a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
@@ -2,9 +2,135 @@ require 'spec_helper'
describe Gitlab::Kubernetes::Helm::InitCommand do
let(:application) { create(:clusters_applications_helm) }
- let(:commands) { 'helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem >/dev/null' }
+ let(:rbac) { false }
+ let(:files) { {} }
+ let(:init_command) { described_class.new(name: application.name, files: files, rbac: rbac) }
- subject { described_class.new(name: application.name, files: {}) }
+ let(:commands) do
+ <<~EOS
+ helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem >/dev/null
+ EOS
+ end
+
+ subject { init_command }
it_behaves_like 'helm commands'
+
+ context 'on a rbac-enabled cluster' do
+ let(:rbac) { true }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem --service-account tiller >/dev/null
+ EOS
+ end
+ end
+ end
+
+ describe '#rbac?' do
+ subject { init_command.rbac? }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#config_map_resource' do
+ let(:metadata) do
+ {
+ name: 'values-content-configuration-helm',
+ namespace: 'gitlab-managed-apps',
+ labels: { name: 'values-content-configuration-helm' }
+ }
+ end
+
+ let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: files) }
+
+ subject { init_command.config_map_resource }
+
+ it 'returns a KubeClient resource with config map content for the application' do
+ is_expected.to eq(resource)
+ end
+ end
+
+ describe '#pod_resource' do
+ subject { init_command.pod_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a pod that uses the tiller serviceAccountName' do
+ expect(subject.spec.serviceAccountName).to eq('tiller')
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates a pod that uses the default serviceAccountName' do
+ expect(subject.spec.serviceAcccountName).to be_nil
+ end
+ end
+ end
+
+ describe '#service_account_resource' do
+ let(:resource) do
+ Kubeclient::Resource.new(metadata: { name: 'tiller', namespace: 'gitlab-managed-apps' })
+ end
+
+ subject { init_command.service_account_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a Kubeclient resource for the tiller ServiceAccount' do
+ is_expected.to eq(resource)
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#cluster_role_binding_resource' do
+ let(:resource) do
+ Kubeclient::Resource.new(
+ metadata: { name: 'tiller-admin' },
+ roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'ClusterRole', name: 'cluster-admin' },
+ subjects: [{ kind: 'ServiceAccount', name: 'tiller', namespace: 'gitlab-managed-apps' }]
+ )
+ end
+
+ subject { init_command.cluster_role_binding_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a Kubeclient resource for the ClusterRoleBinding for tiller' do
+ is_expected.to eq(resource)
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 982e2f41043..f28941ce58f 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -3,14 +3,17 @@ require 'rails_helper'
describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:files) { { 'ca.pem': 'some file content' } }
let(:repository) { 'https://repository.example.com' }
+ let(:rbac) { false }
let(:version) { '1.2.3' }
let(:install_command) do
described_class.new(
name: 'app-name',
chart: 'chart-name',
+ rbac: rbac,
files: files,
- version: version, repository: repository
+ version: version,
+ repository: repository
)
end
@@ -21,19 +24,76 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
<<~EOS
helm init --client-only >/dev/null
helm repo add app-name https://repository.example.com
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null
+ #{helm_install_comand}
+ EOS
+ end
+
+ let(:helm_install_comand) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ --version 1.2.3
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
EOS
end
end
+ context 'when rbac is true' do
+ let(:rbac) { true }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --client-only >/dev/null
+ helm repo add app-name https://repository.example.com
+ #{helm_install_command}
+ EOS
+ end
+
+ let(:helm_install_command) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ --version 1.2.3
+ --set rbac.create\\=true,rbac.enabled\\=true
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
+ EOS
+ end
+ end
+ end
+
context 'when there is no repository' do
let(:repository) { nil }
it_behaves_like 'helm commands' do
let(:commands) do
<<~EOS
- helm init --client-only >/dev/null
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null
+ helm init --client-only >/dev/null
+ #{helm_install_command}
+ EOS
+ end
+
+ let(:helm_install_command) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ --version 1.2.3
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
EOS
end
end
@@ -45,9 +105,19 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
it_behaves_like 'helm commands' do
let(:commands) do
<<~EOS
- helm init --client-only >/dev/null
- helm repo add app-name https://repository.example.com
- helm install chart-name --name app-name --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null
+ helm init --client-only >/dev/null
+ helm repo add app-name https://repository.example.com
+ #{helm_install_command}
+ EOS
+ end
+
+ let(:helm_install_command) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --version 1.2.3
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
EOS
end
end
@@ -59,14 +129,63 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
it_behaves_like 'helm commands' do
let(:commands) do
<<~EOS
- helm init --client-only >/dev/null
- helm repo add app-name https://repository.example.com
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null
+ helm init --client-only >/dev/null
+ helm repo add app-name https://repository.example.com
+ #{helm_install_command}
+ EOS
+ end
+
+ let(:helm_install_command) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
EOS
end
end
end
+ describe '#rbac?' do
+ subject { install_command.rbac? }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#pod_resource' do
+ subject { install_command.pod_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a pod that uses the tiller serviceAccountName' do
+ expect(subject.spec.serviceAccountName).to eq('tiller')
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates a pod that uses the default serviceAccountName' do
+ expect(subject.spec.serviceAcccountName).to be_nil
+ end
+ end
+ end
+
describe '#config_map_resource' do
let(:metadata) do
{
@@ -84,4 +203,20 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
is_expected.to eq(resource)
end
end
+
+ describe '#service_account_resource' do
+ subject { install_command.service_account_resource }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+
+ describe '#cluster_role_binding_resource' do
+ subject { install_command.cluster_role_binding_resource }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index ec64193c0b2..b333b334f36 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -5,8 +5,9 @@ describe Gitlab::Kubernetes::Helm::Pod do
let(:app) { create(:clusters_applications_prometheus) }
let(:command) { app.install_command }
let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
+ let(:service_account_name) { nil }
- subject { described_class.new(command, namespace) }
+ subject { described_class.new(command, namespace, service_account_name: service_account_name) }
context 'with a command' do
it 'should generate a Kubeclient::Resource' do
@@ -58,6 +59,20 @@ describe Gitlab::Kubernetes::Helm::Pod do
expect(volume.configMap['items'].first['key']).to eq(:'values.yaml')
expect(volume.configMap['items'].first['path']).to eq(:'values.yaml')
end
+
+ it 'should have no serviceAccountName' do
+ spec = subject.generate.spec
+ expect(spec.serviceAccountName).to be_nil
+ end
+
+ context 'with a service_account_name' do
+ let(:service_account_name) { 'sa' }
+
+ it 'should use the serviceAccountName provided' do
+ spec = subject.generate.spec
+ expect(spec.serviceAccountName).to eq(service_account_name)
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb
new file mode 100644
index 00000000000..3dabf04413e
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::Kubernetes::Helm::UpgradeCommand do
+ let(:application) { build(:clusters_applications_prometheus) }
+ let(:files) { { 'ca.pem': 'some file content' } }
+ let(:namespace) { ::Gitlab::Kubernetes::Helm::NAMESPACE }
+ let(:rbac) { false }
+ let(:upgrade_command) do
+ described_class.new(
+ application.name,
+ chart: application.chart,
+ files: files,
+ rbac: rbac
+ )
+ end
+
+ subject { upgrade_command }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --client-only >/dev/null
+ helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
+ EOS
+ end
+ end
+
+ context 'rbac is true' do
+ let(:rbac) { true }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --client-only >/dev/null
+ helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
+ EOS
+ end
+ end
+ end
+
+ context 'with an application with a repository' do
+ let(:ci_runner) { create(:ci_runner) }
+ let(:application) { build(:clusters_applications_runner, runner: ci_runner) }
+ let(:upgrade_command) do
+ described_class.new(
+ application.name,
+ chart: application.chart,
+ files: files,
+ rbac: rbac,
+ repository: application.repository
+ )
+ end
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --client-only >/dev/null
+ helm repo add #{application.name} #{application.repository}
+ helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
+ EOS
+ end
+ end
+ end
+
+ context 'when there is no ca.pem file' do
+ let(:files) { { 'file.txt': 'some content' } }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --client-only >/dev/null
+ helm upgrade #{application.name} #{application.chart} --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
+ EOS
+ end
+ end
+ end
+
+ describe '#pod_resource' do
+ subject { upgrade_command.pod_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a pod that uses the tiller serviceAccountName' do
+ expect(subject.spec.serviceAccountName).to eq('tiller')
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates a pod that uses the default serviceAccountName' do
+ expect(subject.spec.serviceAcccountName).to be_nil
+ end
+ end
+ end
+
+ describe '#config_map_resource' do
+ let(:metadata) do
+ {
+ name: "values-content-configuration-#{application.name}",
+ namespace: namespace,
+ labels: { name: "values-content-configuration-#{application.name}" }
+ }
+ end
+ let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: files) }
+
+ it 'returns a KubeClient resource with config map content for the application' do
+ expect(subject.config_map_resource).to eq(resource)
+ end
+ end
+
+ describe '#rbac?' do
+ subject { upgrade_command.rbac? }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#pod_name' do
+ it 'returns the pod name' do
+ expect(subject.pod_name).to eq("upgrade-#{application.name}")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
new file mode 100644
index 00000000000..53c5a4e7c94
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -0,0 +1,249 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::KubeClient do
+ include KubernetesHelpers
+
+ let(:api_url) { 'https://kubernetes.example.com/prefix' }
+ let(:api_groups) { ['api', 'apis/rbac.authorization.k8s.io'] }
+ let(:api_version) { 'v1' }
+ let(:kubeclient_options) { { auth_options: { bearer_token: 'xyz' } } }
+
+ let(:client) { described_class.new(api_url, api_groups, api_version, kubeclient_options) }
+
+ before do
+ stub_kubeclient_discover(api_url)
+ end
+
+ describe '#hashed_clients' do
+ subject { client.hashed_clients }
+
+ it 'has keys from api groups' do
+ expect(subject.keys).to match_array api_groups
+ end
+
+ it 'has values of Kubeclient::Client' do
+ expect(subject.values).to all(be_an_instance_of Kubeclient::Client)
+ end
+ end
+
+ describe '#clients' do
+ subject { client.clients }
+
+ it 'is not empty' do
+ is_expected.to be_present
+ end
+
+ it 'is an array of Kubeclient::Client objects' do
+ is_expected.to all(be_an_instance_of Kubeclient::Client)
+ end
+
+ it 'has each API group url' do
+ expected_urls = api_groups.map { |group| "#{api_url}/#{group}" }
+
+ expect(subject.map(&:api_endpoint).map(&:to_s)).to match_array(expected_urls)
+ end
+
+ it 'has the kubeclient options' do
+ subject.each do |client|
+ expect(client.auth_options).to eq({ bearer_token: 'xyz' })
+ end
+ end
+
+ it 'has the api_version' do
+ subject.each do |client|
+ expect(client.instance_variable_get(:@api_version)).to eq('v1')
+ end
+ end
+ end
+
+ describe '#core_client' do
+ subject { client.core_client }
+
+ it 'is a Kubeclient::Client' do
+ is_expected.to be_an_instance_of Kubeclient::Client
+ end
+
+ it 'has the core API endpoint' do
+ expect(subject.api_endpoint.to_s).to match(%r{\/api\Z})
+ end
+ end
+
+ describe '#rbac_client' do
+ subject { client.rbac_client }
+
+ it 'is a Kubeclient::Client' do
+ is_expected.to be_an_instance_of Kubeclient::Client
+ end
+
+ it 'has the RBAC API group endpoint' do
+ expect(subject.api_endpoint.to_s).to match(%r{\/apis\/rbac.authorization.k8s.io\Z})
+ end
+ end
+
+ describe '#extensions_client' do
+ subject { client.extensions_client }
+
+ let(:api_groups) { ['apis/extensions'] }
+
+ it 'is a Kubeclient::Client' do
+ is_expected.to be_an_instance_of Kubeclient::Client
+ end
+
+ it 'has the extensions API group endpoint' do
+ expect(subject.api_endpoint.to_s).to match(%r{\/apis\/extensions\Z})
+ end
+ end
+
+ describe '#discover!' do
+ it 'makes a discovery request for each API group' do
+ client.discover!
+
+ api_groups.each do |api_group|
+ discovery_url = api_url + '/' + api_group + '/v1'
+ expect(WebMock).to have_requested(:get, discovery_url).once
+ end
+ end
+ end
+
+ describe 'core API' do
+ let(:core_client) { client.core_client }
+
+ [
+ :get_pods,
+ :get_secrets,
+ :get_config_map,
+ :get_pod,
+ :get_namespace,
+ :get_secret,
+ :get_service,
+ :get_service_account,
+ :delete_pod,
+ :create_config_map,
+ :create_namespace,
+ :create_pod,
+ :create_secret,
+ :create_service_account,
+ :update_config_map,
+ :update_service_account
+ ].each do |method|
+ describe "##{method}" do
+ it 'delegates to the core client' do
+ expect(client).to delegate_method(method).to(:core_client)
+ end
+
+ it 'responds to the method' do
+ expect(client).to respond_to method
+ end
+ end
+ end
+ end
+
+ describe 'rbac API group' do
+ let(:rbac_client) { client.rbac_client }
+
+ [
+ :create_cluster_role_binding,
+ :get_cluster_role_binding,
+ :update_cluster_role_binding
+ ].each do |method|
+ describe "##{method}" do
+ it 'delegates to the rbac client' do
+ expect(client).to delegate_method(method).to(:rbac_client)
+ end
+
+ it 'responds to the method' do
+ expect(client).to respond_to method
+ end
+
+ context 'no rbac client' do
+ let(:api_groups) { ['api'] }
+
+ it 'throws an error' do
+ expect { client.public_send(method) }.to raise_error(Module::DelegationError)
+ end
+ end
+ end
+ end
+ end
+
+ describe 'extensions API group' do
+ let(:api_groups) { ['apis/extensions'] }
+ let(:api_version) { 'v1beta1' }
+ let(:extensions_client) { client.extensions_client }
+
+ describe '#get_deployments' do
+ it 'delegates to the extensions client' do
+ expect(client).to delegate_method(:get_deployments).to(:extensions_client)
+ end
+
+ it 'responds to the method' do
+ expect(client).to respond_to :get_deployments
+ end
+
+ context 'no extensions client' do
+ let(:api_groups) { ['api'] }
+ let(:api_version) { 'v1' }
+
+ it 'throws an error' do
+ expect { client.get_deployments }.to raise_error(Module::DelegationError)
+ end
+ end
+ end
+ end
+
+ describe 'non-entity methods' do
+ it 'does not proxy for non-entity methods' do
+ expect(client.clients.first).to respond_to :proxy_url
+
+ expect(client).not_to respond_to :proxy_url
+ end
+
+ it 'throws an error' do
+ expect { client.proxy_url }.to raise_error(NoMethodError)
+ end
+ end
+
+ describe '#get_pod_log' do
+ let(:core_client) { client.core_client }
+
+ it 'is delegated to the core client' do
+ expect(client).to delegate_method(:get_pod_log).to(:core_client)
+ end
+
+ context 'when no core client' do
+ let(:api_groups) { ['apis/extensions'] }
+
+ it 'throws an error' do
+ expect { client.get_pod_log('pod-name') }.to raise_error(Module::DelegationError)
+ end
+ end
+ end
+
+ describe '#watch_pod_log' do
+ let(:core_client) { client.core_client }
+
+ it 'is delegated to the core client' do
+ expect(client).to delegate_method(:watch_pod_log).to(:core_client)
+ end
+
+ context 'when no core client' do
+ let(:api_groups) { ['apis/extensions'] }
+
+ it 'throws an error' do
+ expect { client.watch_pod_log('pod-name') }.to raise_error(Module::DelegationError)
+ end
+ end
+ end
+
+ describe 'methods that do not exist on any client' do
+ it 'throws an error' do
+ expect { client.non_existent_method }.to raise_error(NoMethodError)
+ end
+
+ it 'returns false for respond_to' do
+ expect(client.respond_to?(:non_existent_method)).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/service_account_spec.rb b/spec/lib/gitlab/kubernetes/service_account_spec.rb
new file mode 100644
index 00000000000..8da9e932dc3
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/service_account_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::ServiceAccount do
+ let(:name) { 'a_service_account' }
+ let(:namespace_name) { 'a_namespace' }
+ let(:service_account) { described_class.new(name, namespace_name) }
+
+ it { expect(service_account.name).to eq(name) }
+ it { expect(service_account.namespace_name).to eq(namespace_name) }
+
+ describe '#generate' do
+ let(:resource) do
+ ::Kubeclient::Resource.new(metadata: { name: name, namespace: namespace_name })
+ end
+
+ subject { service_account.generate }
+
+ it 'should build a Kubeclient Resource' do
+ is_expected.to eq(resource)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
new file mode 100644
index 00000000000..0773d3d9aec
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::ServiceAccountToken do
+ let(:name) { 'token-name' }
+ let(:service_account_name) { 'a_service_account' }
+ let(:namespace_name) { 'a_namespace' }
+ let(:service_account_token) { described_class.new(name, service_account_name, namespace_name) }
+
+ it { expect(service_account_token.name).to eq(name) }
+ it { expect(service_account_token.service_account_name).to eq(service_account_name) }
+ it { expect(service_account_token.namespace_name).to eq(namespace_name) }
+
+ describe '#generate' do
+ let(:resource) do
+ ::Kubeclient::Resource.new(
+ metadata: {
+ name: name,
+ namespace: namespace_name,
+ annotations: {
+ 'kubernetes.io/service-account.name': service_account_name
+ }
+ },
+ type: 'kubernetes.io/service-account-token'
+ )
+ end
+
+ subject { service_account_token.generate }
+
+ it 'should build a Kubeclient Resource' do
+ is_expected.to eq(resource)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index 4e7bd433a9c..ee6d6fc961f 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -42,6 +42,65 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
subscriber.sql(event)
end
+
+ context 'events are internal to Rails or irrelevant' do
+ let(:schema_event) do
+ double(
+ :event,
+ name: 'sql.active_record',
+ payload: {
+ sql: "SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '\"projects\"'::regclass",
+ name: 'SCHEMA',
+ connection_id: 135,
+ statement_name: nil,
+ binds: []
+ },
+ duration: 0.7
+ )
+ end
+
+ let(:begin_event) do
+ double(
+ :event,
+ name: 'sql.active_record',
+ payload: {
+ sql: "BEGIN",
+ name: nil,
+ connection_id: 231,
+ statement_name: nil,
+ binds: []
+ },
+ duration: 1.1
+ )
+ end
+
+ let(:commit_event) do
+ double(
+ :event,
+ name: 'sql.active_record',
+ payload: {
+ sql: "COMMIT",
+ name: nil,
+ connection_id: 212,
+ statement_name: nil,
+ binds: []
+ },
+ duration: 1.6
+ )
+ end
+
+ it 'skips schema/begin/commit sql commands' do
+ expect(subscriber).to receive(:current_transaction)
+ .at_least(:once)
+ .and_return(transaction)
+
+ expect(transaction).not_to receive(:increment)
+
+ subscriber.sql(schema_event)
+ subscriber.sql(begin_event)
+ subscriber.sql(commit_event)
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index 8fbeaa065fa..ac3bc6b2dfe 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::Middleware::ReadOnly do
end
end
- context 'normal requests to a read-only Gitlab instance' do
+ context 'normal requests to a read-only GitLab instance' do
let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] } }
before do
diff --git a/spec/lib/gitlab/null_request_store_spec.rb b/spec/lib/gitlab/null_request_store_spec.rb
new file mode 100644
index 00000000000..c023dac53ad
--- /dev/null
+++ b/spec/lib/gitlab/null_request_store_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::NullRequestStore do
+ let(:null_store) { described_class.new }
+
+ describe '#store' do
+ it 'returns an empty hash' do
+ expect(null_store.store).to eq({})
+ end
+ end
+
+ describe '#active?' do
+ it 'returns falsey' do
+ expect(null_store.active?).to be_falsey
+ end
+ end
+
+ describe '#read' do
+ it 'returns nil' do
+ expect(null_store.read('foo')).to be nil
+ end
+ end
+
+ describe '#[]' do
+ it 'returns nil' do
+ expect(null_store['foo']).to be nil
+ end
+ end
+
+ describe '#write' do
+ it 'returns the same value' do
+ expect(null_store.write('key', 'value')).to eq('value')
+ end
+ end
+
+ describe '#[]=' do
+ it 'returns the same value' do
+ expect(null_store['key'] = 'value').to eq('value')
+ end
+ end
+
+ describe '#exist?' do
+ it 'returns falsey' do
+ expect(null_store.exist?('foo')).to be_falsey
+ end
+ end
+
+ describe '#fetch' do
+ it 'returns the block result' do
+ expect(null_store.fetch('key') { 'block result' }).to eq('block result')
+ end
+ end
+
+ describe '#delete' do
+ context 'when a block is given' do
+ it 'yields the key to the block' do
+ expect do |b|
+ null_store.delete('foo', &b)
+ end.to yield_with_args('foo')
+ end
+
+ it 'returns the block result' do
+ expect(null_store.delete('foo') { |key| 'block result' }).to eq('block result')
+ end
+ end
+
+ context 'when a block is not given' do
+ it 'returns nil' do
+ expect(null_store.delete('foo')).to be nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/patch/prependable_spec.rb b/spec/lib/gitlab/patch/prependable_spec.rb
new file mode 100644
index 00000000000..725d733d176
--- /dev/null
+++ b/spec/lib/gitlab/patch/prependable_spec.rb
@@ -0,0 +1,234 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+# Patching ActiveSupport::Concern
+require_relative '../../../../config/initializers/0_as_concern'
+
+describe Gitlab::Patch::Prependable do
+ before do
+ @prepended_modules = []
+ end
+
+ let(:ee) do
+ # So that block in Module.new could see them
+ prepended_modules = @prepended_modules
+
+ Module.new do
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def class_name
+ super.tr('C', 'E')
+ end
+ end
+
+ this = self
+ prepended do
+ prepended_modules << [self, this]
+ end
+
+ def name
+ super.tr('c', 'e')
+ end
+ end
+ end
+
+ let(:ce) do
+ # So that block in Module.new could see them
+ prepended_modules = @prepended_modules
+ ee_ = ee
+
+ Module.new do
+ extend ActiveSupport::Concern
+ prepend ee_
+
+ class_methods do
+ def class_name
+ 'CE'
+ end
+ end
+
+ this = self
+ prepended do
+ prepended_modules << [self, this]
+ end
+
+ def name
+ 'ce'
+ end
+ end
+ end
+
+ describe 'a class including a concern prepending a concern' do
+ subject { Class.new.include(ce) }
+
+ it 'returns values from prepended module ee' do
+ expect(subject.new.name).to eq('ee')
+ expect(subject.class_name).to eq('EE')
+ end
+
+ it 'has the expected ancestors' do
+ expect(subject.ancestors.take(3)).to eq([subject, ee, ce])
+ expect(subject.singleton_class.ancestors.take(3))
+ .to eq([subject.singleton_class,
+ ee.const_get(:ClassMethods),
+ ce.const_get(:ClassMethods)])
+ end
+
+ it 'prepends only once even if called twice' do
+ 2.times { ce.prepend(ee) }
+
+ subject
+
+ expect(@prepended_modules).to eq([[ce, ee]])
+ end
+
+ context 'overriding methods' do
+ before do
+ subject.module_eval do
+ def self.class_name
+ 'Custom'
+ end
+
+ def name
+ 'custom'
+ end
+ end
+ end
+
+ it 'returns values from the class' do
+ expect(subject.new.name).to eq('custom')
+ expect(subject.class_name).to eq('Custom')
+ end
+ end
+ end
+
+ describe 'a class prepending a concern prepending a concern' do
+ subject { Class.new.prepend(ce) }
+
+ it 'returns values from prepended module ee' do
+ expect(subject.new.name).to eq('ee')
+ expect(subject.class_name).to eq('EE')
+ end
+
+ it 'has the expected ancestors' do
+ expect(subject.ancestors.take(3)).to eq([ee, ce, subject])
+ expect(subject.singleton_class.ancestors.take(3))
+ .to eq([ee.const_get(:ClassMethods),
+ ce.const_get(:ClassMethods),
+ subject.singleton_class])
+ end
+
+ it 'prepends only once' do
+ subject.prepend(ce)
+
+ expect(@prepended_modules).to eq([[ce, ee], [subject, ce]])
+ end
+ end
+
+ describe 'a class prepending a concern' do
+ subject do
+ ee_ = ee
+
+ Class.new do
+ prepend ee_
+
+ def self.class_name
+ 'CE'
+ end
+
+ def name
+ 'ce'
+ end
+ end
+ end
+
+ it 'returns values from prepended module ee' do
+ expect(subject.new.name).to eq('ee')
+ expect(subject.class_name).to eq('EE')
+ end
+
+ it 'has the expected ancestors' do
+ expect(subject.ancestors.take(2)).to eq([ee, subject])
+ expect(subject.singleton_class.ancestors.take(2))
+ .to eq([ee.const_get(:ClassMethods),
+ subject.singleton_class])
+ end
+
+ it 'prepends only once' do
+ subject.prepend(ee)
+
+ expect(@prepended_modules).to eq([[subject, ee]])
+ end
+ end
+
+ describe 'simple case' do
+ subject do
+ foo_ = foo
+
+ Class.new do
+ prepend foo_
+
+ def value
+ 10
+ end
+ end
+ end
+
+ let(:foo) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ prepended do
+ def self.class_value
+ 20
+ end
+ end
+
+ def value
+ super * 10
+ end
+ end
+ end
+
+ context 'class methods' do
+ it "has a method" do
+ expect(subject).to respond_to(:class_value)
+ end
+
+ it 'can execute a method' do
+ expect(subject.class_value).to eq(20)
+ end
+ end
+
+ context 'instance methods' do
+ it "has a method" do
+ expect(subject.new).to respond_to(:value)
+ end
+
+ it 'chains a method execution' do
+ expect(subject.new.value).to eq(100)
+ end
+ end
+ end
+
+ context 'having two prepended blocks' do
+ subject do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ prepended do
+ end
+
+ prepended do
+ end
+ end
+ end
+
+ it "raises an error" do
+ expect { subject }
+ .to raise_error(described_class::MultiplePrependedBlocks)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
index 5589db92b1d..1a108003bc2 100644
--- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Prometheus::AdditionalMetricsParser do
let(:parser_error_class) { Gitlab::Prometheus::ParsingError }
describe '#load_groups_from_yaml' do
- subject { described_class.load_groups_from_yaml }
+ subject { described_class.load_groups_from_yaml('dummy.yaml') }
describe 'parsing sample yaml' do
let(:sample_yaml) do
diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb
new file mode 100644
index 00000000000..e7d16e73663
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::Prometheus::MetricGroup do
+ describe '.common_metrics' do
+ let!(:project_metric) { create(:prometheus_metric) }
+ let!(:common_metric_group_a) { create(:prometheus_metric, :common, group: :aws_elb) }
+ let!(:common_metric_group_b_q1) { create(:prometheus_metric, :common, group: :kubernetes) }
+ let!(:common_metric_group_b_q2) { create(:prometheus_metric, :common, group: :kubernetes) }
+
+ subject { described_class.common_metrics }
+
+ it 'returns exactly two groups' do
+ expect(subject.map(&:name)).to contain_exactly(
+ 'Response metrics (AWS ELB)', 'System metrics (Kubernetes)')
+ end
+
+ it 'returns exactly three metric queries' do
+ expect(subject.map(&:metrics).flatten.map(&:id)).to contain_exactly(
+ common_metric_group_a.id, common_metric_group_b_q1.id,
+ common_metric_group_b_q2.id)
+ end
+ end
+
+ describe '.for_project' do
+ let!(:other_project) { create(:project) }
+ let!(:project_metric) { create(:prometheus_metric) }
+ let!(:common_metric) { create(:prometheus_metric, :common, group: :aws_elb) }
+
+ subject do
+ described_class.for_project(other_project)
+ .map(&:metrics).flatten
+ .map(&:id)
+ end
+
+ it 'returns exactly one common metric' do
+ is_expected.to contain_exactly(common_metric.id)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 5bd4d6c6a48..0295138fc3a 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -65,6 +65,144 @@ describe Gitlab::RepositoryCacheAdapter do
end
end
+ describe '#cache_method_output_asymmetrically', :use_clean_rails_memory_store_caching, :request_store do
+ let(:request_store_cache) { repository.send(:request_store_cache) }
+
+ context 'with a non-existing repository' do
+ let(:project) { create(:project) } # No repository
+ let(:object) { double }
+
+ subject do
+ repository.cache_method_output_asymmetrically(:cats) do
+ object.cats_call_stub
+ end
+ end
+
+ it 'returns the output of the original method' do
+ expect(object).to receive(:cats_call_stub).and_return('output')
+
+ expect(subject).to eq('output')
+ end
+ end
+
+ context 'with a method throwing a non-existing-repository error' do
+ subject do
+ repository.cache_method_output_asymmetrically(:cats) do
+ raise Gitlab::Git::Repository::NoRepository
+ end
+ end
+
+ it 'returns nil' do
+ expect(subject).to eq(nil)
+ end
+
+ it 'does not cache the data' do
+ subject
+
+ expect(repository.instance_variable_defined?(:@cats)).to eq(false)
+ expect(cache.exist?(:cats)).to eq(false)
+ end
+ end
+
+ context 'with an existing repository' do
+ let(:object) { double }
+
+ context 'when it returns truthy' do
+ before do
+ expect(object).to receive(:cats).once.and_return('truthy output')
+ end
+
+ it 'caches the output in RequestStore' do
+ expect do
+ repository.cache_method_output_asymmetrically(:cats) { object.cats }
+ end.to change { request_store_cache.read(:cats) }.from(nil).to('truthy output')
+ end
+
+ it 'caches the output in RepositoryCache' do
+ expect do
+ repository.cache_method_output_asymmetrically(:cats) { object.cats }
+ end.to change { cache.read(:cats) }.from(nil).to('truthy output')
+ end
+ end
+
+ context 'when it returns false' do
+ before do
+ expect(object).to receive(:cats).once.and_return(false)
+ end
+
+ it 'caches the output in RequestStore' do
+ expect do
+ repository.cache_method_output_asymmetrically(:cats) { object.cats }
+ end.to change { request_store_cache.read(:cats) }.from(nil).to(false)
+ end
+
+ it 'does NOT cache the output in RepositoryCache' do
+ expect do
+ repository.cache_method_output_asymmetrically(:cats) { object.cats }
+ end.not_to change { cache.read(:cats) }.from(nil)
+ end
+ end
+ end
+ end
+
+ describe '#memoize_method_output' do
+ let(:fallback) { 10 }
+
+ context 'with a non-existing repository' do
+ let(:project) { create(:project) } # No repository
+
+ subject do
+ repository.memoize_method_output(:cats, fallback: fallback) do
+ repository.cats_call_stub
+ end
+ end
+
+ it 'returns the fallback value' do
+ expect(subject).to eq(fallback)
+ end
+
+ it 'avoids calling the original method' do
+ expect(repository).not_to receive(:cats_call_stub)
+
+ subject
+ end
+
+ it 'does not set the instance variable' do
+ subject
+
+ expect(repository.instance_variable_defined?(:@cats)).to eq(false)
+ end
+ end
+
+ context 'with a method throwing a non-existing-repository error' do
+ subject do
+ repository.memoize_method_output(:cats, fallback: fallback) do
+ raise Gitlab::Git::Repository::NoRepository
+ end
+ end
+
+ it 'returns the fallback value' do
+ expect(subject).to eq(fallback)
+ end
+
+ it 'does not set the instance variable' do
+ subject
+
+ expect(repository.instance_variable_defined?(:@cats)).to eq(false)
+ end
+ end
+
+ context 'with an existing repository' do
+ it 'sets the instance variable' do
+ repository.memoize_method_output(:cats, fallback: fallback) do
+ 'block output'
+ end
+
+ expect(repository.instance_variable_get(:@cats)).to eq('block output')
+ end
+ end
+ end
+
describe '#expire_method_caches' do
it 'expires the caches of the given methods' do
expect(cache).to receive(:expire).with(:rendered_readme)
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
index fc259cf1208..741ee12633f 100644
--- a/spec/lib/gitlab/repository_cache_spec.rb
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -47,4 +47,89 @@ describe Gitlab::RepositoryCache do
expect(backend).to have_received(:fetch).with("baz:#{namespace}", &p)
end
end
+
+ describe '#fetch_without_caching_false', :use_clean_rails_memory_store_caching do
+ let(:key) { :foo }
+ let(:backend) { Rails.cache }
+
+ it 'requires a block' do
+ expect do
+ cache.fetch_without_caching_false(key)
+ end.to raise_error(LocalJumpError)
+ end
+
+ context 'when the key does not exist in the cache' do
+ context 'when the result of the block is truthy' do
+ it 'returns the result of the block' do
+ result = cache.fetch_without_caching_false(key) { true }
+
+ expect(result).to be true
+ end
+
+ it 'caches the value' do
+ expect(backend).to receive(:write).with("#{key}:#{namespace}", true)
+
+ cache.fetch_without_caching_false(key) { true }
+ end
+ end
+
+ context 'when the result of the block is falsey' do
+ let(:p) { -> { false } }
+
+ it 'returns the result of the block' do
+ result = cache.fetch_without_caching_false(key, &p)
+
+ expect(result).to be false
+ end
+
+ it 'does not cache the value' do
+ expect(backend).not_to receive(:write).with("#{key}:#{namespace}", true)
+
+ cache.fetch_without_caching_false(key, &p)
+ end
+ end
+ end
+
+ context 'when the cached value is truthy' do
+ before do
+ backend.write("#{key}:#{namespace}", true)
+ end
+
+ it 'returns the cached value' do
+ result = cache.fetch_without_caching_false(key) { 'block result' }
+
+ expect(result).to be true
+ end
+
+ it 'does not execute the block' do
+ expect do |b|
+ cache.fetch_without_caching_false(key, &b)
+ end.not_to yield_control
+ end
+
+ it 'does not write to the cache' do
+ expect(backend).not_to receive(:write)
+
+ cache.fetch_without_caching_false(key) { 'block result' }
+ end
+ end
+
+ context 'when the cached value is falsey' do
+ before do
+ backend.write("#{key}:#{namespace}", false)
+ end
+
+ it 'returns the result of the block' do
+ result = cache.fetch_without_caching_false(key) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'writes the truthy value to the cache' do
+ expect(backend).to receive(:write).with("#{key}:#{namespace}", 'block result')
+
+ cache.fetch_without_caching_false(key) { 'block result' }
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/safe_request_store_spec.rb b/spec/lib/gitlab/safe_request_store_spec.rb
new file mode 100644
index 00000000000..c797171dbe2
--- /dev/null
+++ b/spec/lib/gitlab/safe_request_store_spec.rb
@@ -0,0 +1,245 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SafeRequestStore do
+ describe '.store' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect(described_class.store).to eq(RequestStore)
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'does not use RequestStore' do
+ expect(described_class.store).to be_a(Gitlab::NullRequestStore)
+ end
+ end
+ end
+
+ describe '.begin!' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect(RequestStore).to receive(:begin!)
+
+ described_class.begin!
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'uses RequestStore' do
+ expect(RequestStore).to receive(:begin!)
+
+ described_class.begin!
+ end
+ end
+ end
+
+ describe '.clear!' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect(RequestStore).to receive(:clear!).twice.and_call_original
+
+ described_class.clear!
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'uses RequestStore' do
+ expect(RequestStore).to receive(:clear!).and_call_original
+
+ described_class.clear!
+ end
+ end
+ end
+
+ describe '.end!' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect(RequestStore).to receive(:end!).twice.and_call_original
+
+ described_class.end!
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'uses RequestStore' do
+ expect(RequestStore).to receive(:end!).and_call_original
+
+ described_class.end!
+ end
+ end
+ end
+
+ describe '.write' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect do
+ described_class.write('foo', true)
+ end.to change { described_class.read('foo') }.from(nil).to(true)
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'does not use RequestStore' do
+ expect do
+ described_class.write('foo', true)
+ end.not_to change { described_class.read('foo') }.from(nil)
+ end
+ end
+ end
+
+ describe '.[]=' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect do
+ described_class['foo'] = true
+ end.to change { described_class.read('foo') }.from(nil).to(true)
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'does not use RequestStore' do
+ expect do
+ described_class['foo'] = true
+ end.not_to change { described_class.read('foo') }.from(nil)
+ end
+ end
+ end
+
+ describe '.read' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect do
+ RequestStore.write('foo', true)
+ end.to change { described_class.read('foo') }.from(nil).to(true)
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'does not use RequestStore' do
+ expect do
+ RequestStore.write('foo', true)
+ end.not_to change { described_class.read('foo') }.from(nil)
+
+ RequestStore.clear! # Clean up
+ end
+ end
+ end
+
+ describe '.[]' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect do
+ RequestStore.write('foo', true)
+ end.to change { described_class['foo'] }.from(nil).to(true)
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'does not use RequestStore' do
+ expect do
+ RequestStore.write('foo', true)
+ end.not_to change { described_class['foo'] }.from(nil)
+
+ RequestStore.clear! # Clean up
+ end
+ end
+ end
+
+ describe '.exist?' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect do
+ RequestStore.write('foo', 'not nil')
+ end.to change { described_class.exist?('foo') }.from(false).to(true)
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'does not use RequestStore' do
+ expect do
+ RequestStore.write('foo', 'not nil')
+ end.not_to change { described_class.exist?('foo') }.from(false)
+
+ RequestStore.clear! # Clean up
+ end
+ end
+ end
+
+ describe '.fetch' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ expect do
+ described_class.fetch('foo') { 'block result' }
+ end.to change { described_class.read('foo') }.from(nil).to('block result')
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ it 'does not use RequestStore' do
+ RequestStore.clear! # Ensure clean
+
+ expect do
+ described_class.fetch('foo') { 'block result' }
+ end.not_to change { described_class.read('foo') }.from(nil)
+
+ RequestStore.clear! # Clean up
+ end
+ end
+ end
+
+ describe '.delete' do
+ context 'when RequestStore is active', :request_store do
+ it 'uses RequestStore' do
+ described_class.write('foo', true)
+
+ expect do
+ described_class.delete('foo')
+ end.to change { described_class.read('foo') }.from(true).to(nil)
+ end
+
+ context 'when given a block and the key exists' do
+ it 'does not execute the block' do
+ described_class.write('foo', true)
+
+ expect do |b|
+ described_class.delete('foo', &b)
+ end.not_to yield_control
+ end
+ end
+
+ context 'when given a block and the key does not exist' do
+ it 'yields the key and returns the block result' do
+ result = described_class.delete('foo') { |key| "#{key} block result" }
+
+ expect(result).to eq('foo block result')
+ end
+ end
+ end
+
+ context 'when RequestStore is NOT active' do
+ before do
+ RequestStore.write('foo', true)
+ end
+
+ after do
+ RequestStore.clear! # Clean up
+ end
+
+ it 'does not use RequestStore' do
+ expect do
+ described_class.delete('foo')
+ end.not_to change { RequestStore.read('foo') }.from(true)
+ end
+
+ context 'when given a block' do
+ it 'yields the key and returns the block result' do
+ result = described_class.delete('foo') { |key| "#{key} block result" }
+
+ expect(result).to eq('foo block result')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index f8bf896950e..b1b7c427313 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -7,15 +7,10 @@ describe Gitlab::Shell do
let(:repository) { project.repository }
let(:gitlab_shell) { described_class.new }
let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
- let(:gitlab_projects) { double('gitlab_projects') }
let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
before do
allow(Project).to receive(:find).and_return(project)
-
- allow(gitlab_shell).to receive(:gitlab_projects)
- .with(project.repository_storage, project.disk_path + '.git')
- .and_return(gitlab_projects)
end
it { is_expected.to respond_to :add_key }
diff --git a/spec/lib/gitlab/sidekiq_throttler_spec.rb b/spec/lib/gitlab/sidekiq_throttler_spec.rb
deleted file mode 100644
index 2dbb7bb7c34..00000000000
--- a/spec/lib/gitlab/sidekiq_throttler_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::SidekiqThrottler do
- describe '#execute!' do
- context 'when job throttling is enabled' do
- before do
- Sidekiq.options[:concurrency] = 35
-
- stub_application_setting(
- sidekiq_throttling_enabled: true,
- sidekiq_throttling_factor: 0.1,
- sidekiq_throttling_queues: %w[build project_cache]
- )
- end
-
- it 'requires sidekiq-limit_fetch' do
- expect(described_class).to receive(:require).with('sidekiq-limit_fetch').and_call_original
-
- described_class.execute!
- end
-
- it 'sets limits on the selected queues' do
- described_class.execute!
-
- expect(Sidekiq::Queue['build'].limit).to eq 4
- expect(Sidekiq::Queue['project_cache'].limit).to eq 4
- end
-
- it 'does not set limits on other queues' do
- described_class.execute!
-
- expect(Sidekiq::Queue['merge'].limit).to be_nil
- end
- end
-
- context 'when job throttling is disabled' do
- it 'does not require sidekiq-limit_fetch' do
- expect(described_class).not_to receive(:require).with('sidekiq-limit_fetch')
-
- described_class.execute!
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index e2fa76522bc..fe46c67a920 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -40,7 +40,7 @@ describe Gitlab::Template::GitlabCiYmlTemplate do
describe '#content' do
it 'loads the full file' do
- gitignore = subject.new(Rails.root.join('vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml'))
+ gitignore = subject.new(Rails.root.join('lib/gitlab/ci/templates/Ruby.gitlab-ci.yml'))
expect(gitignore.name).to eq 'Ruby'
expect(gitignore.content).to start_with('#')
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
new file mode 100644
index 00000000000..7ffcef2baef
--- /dev/null
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -0,0 +1,202 @@
+require 'spec_helper'
+
+describe Gitlab::TreeSummary do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:project) { create(:project, :empty_repo) }
+ let(:repo) { project.repository }
+ let(:commit) { repo.head_commit }
+
+ let(:path) { nil }
+ let(:offset) { nil }
+ let(:limit) { nil }
+
+ subject(:summary) { described_class.new(commit, project, path: path, offset: offset, limit: limit) }
+
+ describe '#initialize' do
+ it 'defaults offset to 0' do
+ expect(summary.offset).to eq(0)
+ end
+
+ it 'defaults limit to 25' do
+ expect(summary.limit).to eq(25)
+ end
+ end
+
+ describe '#summarize' do
+ let(:project) { create(:project, :custom_repo, files: { 'a.txt' => '' }) }
+
+ subject(:summarized) { summary.summarize }
+
+ it 'returns an array of entries, and an array of commits' do
+ expect(summarized).to be_a(Array)
+ expect(summarized.size).to eq(2)
+
+ entries, commits = *summarized
+ aggregate_failures do
+ expect(entries).to contain_exactly(
+ a_hash_including(file_name: 'a.txt', commit: have_attributes(id: commit.id))
+ )
+
+ expect(commits).to match_array(entries.map { |entry| entry[:commit] })
+ end
+ end
+ end
+
+ describe '#summarize (entries)' do
+ let(:limit) { 2 }
+
+ custom_files = {
+ 'a.txt' => '',
+ 'b.txt' => '',
+ 'directory/c.txt' => ''
+ }
+
+ let(:project) { create(:project, :custom_repo, files: custom_files) }
+ let(:commit) { repo.head_commit }
+
+ subject(:entries) { summary.summarize.first }
+
+ it 'summarizes the entries within the window' do
+ is_expected.to contain_exactly(
+ a_hash_including(type: :tree, file_name: 'directory'),
+ a_hash_including(type: :blob, file_name: 'a.txt')
+ # b.txt is excluded by the limit
+ )
+ end
+
+ it 'references the commit and commit path in entries' do
+ entry = entries.first
+ expected_commit_path = Gitlab::Routing.url_helpers.project_commit_path(project, commit)
+
+ expect(entry[:commit]).to be_a(::Commit)
+ expect(entry[:commit_path]).to eq expected_commit_path
+ end
+
+ context 'in a good subdirectory' do
+ let(:path) { 'directory' }
+
+ it 'summarizes the entries in the subdirectory' do
+ is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'c.txt'))
+ end
+ end
+
+ context 'in a non-existent subdirectory' do
+ let(:path) { 'tmp' }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'custom offset and limit' do
+ let(:offset) { 2 }
+
+ it 'returns entries from the offset' do
+ is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'b.txt'))
+ end
+ end
+ end
+
+ describe '#summarize (commits)' do
+ # This is a commit in the master branch of the gitlab-test repository that
+ # satisfies certain assumptions these tests depend on
+ let(:test_commit_sha) { '7975be0116940bf2ad4321f79d02a55c5f7779aa' }
+ let(:whitespace_commit_sha) { '66eceea0db202bb39c4e445e8ca28689645366c5' }
+
+ let(:project) { create(:project, :repository) }
+ let(:commit) { repo.commit(test_commit_sha) }
+ let(:limit) { nil }
+ let(:entries) { summary.summarize.first }
+
+ subject(:commits) do
+ summary.summarize.last
+ end
+
+ it 'returns an Array of ::Commit objects' do
+ is_expected.not_to be_empty
+ is_expected.to all(be_kind_of(::Commit))
+ end
+
+ it 'deduplicates commits when multiple entries reference the same commit' do
+ expect(commits.size).to be < entries.size
+ end
+
+ context 'in a subdirectory' do
+ let(:path) { 'files' }
+
+ it 'returns commits for entries in the subdirectory' do
+ expect(commits).to satisfy_one { |c| c.id == whitespace_commit_sha }
+ end
+ end
+ end
+
+ describe '#more?' do
+ let(:path) { 'tmp/more' }
+
+ where(:num_entries, :offset, :limit, :expected_result) do
+ 0 | 0 | 0 | false
+ 0 | 0 | 1 | false
+
+ 1 | 0 | 0 | true
+ 1 | 0 | 1 | false
+ 1 | 1 | 0 | false
+ 1 | 1 | 1 | false
+
+ 2 | 0 | 0 | true
+ 2 | 0 | 1 | true
+ 2 | 0 | 2 | false
+ 2 | 0 | 3 | false
+ 2 | 1 | 0 | true
+ 2 | 1 | 1 | false
+ 2 | 2 | 0 | false
+ 2 | 2 | 1 | false
+ end
+
+ with_them do
+ before do
+ create_file('dummy', path: 'other') if num_entries.zero?
+ 1.upto(num_entries) { |n| create_file(n, path: path) }
+ end
+
+ subject { summary.more? }
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '#next_offset' do
+ let(:path) { 'tmp/next_offset' }
+
+ where(:num_entries, :offset, :limit, :expected_result) do
+ 0 | 0 | 0 | 0
+ 0 | 0 | 1 | 1
+ 0 | 1 | 0 | 1
+ 0 | 1 | 1 | 1
+
+ 1 | 0 | 0 | 0
+ 1 | 0 | 1 | 1
+ 1 | 1 | 0 | 1
+ 1 | 1 | 1 | 2
+ end
+
+ with_them do
+ before do
+ create_file('dummy', path: 'other') if num_entries.zero?
+ 1.upto(num_entries) { |n| create_file(n, path: path) }
+ end
+
+ subject { summary.next_offset }
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ def create_file(unique, path:)
+ repo.create_file(
+ project.creator,
+ "#{path}/file-#{unique}.txt",
+ 'content',
+ message: "Commit message #{unique}",
+ branch_name: 'master'
+ )
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index de6dd2a9fea..d669c42ab4a 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -46,6 +46,7 @@ describe Gitlab::UsageData do
git
database
avg_cycle_analytics
+ web_ide_commits
))
end
@@ -166,4 +167,20 @@ describe Gitlab::UsageData do
expect(subject[:recorded_at]).to be_a(Time)
end
end
+
+ describe '#count' do
+ let(:relation) { double(:relation) }
+
+ it 'returns the count when counting succeeds' do
+ allow(relation).to receive(:count).and_return(1)
+
+ expect(described_class.count(relation)).to eq(1)
+ end
+
+ it 'returns the fallback value when counting fails' do
+ allow(relation).to receive(:count).and_raise(ActiveRecord::StatementInvalid.new(''))
+
+ expect(described_class.count(relation, fallback: 15)).to eq(15)
+ end
+ end
end
diff --git a/spec/lib/gitlab/user_extractor_spec.rb b/spec/lib/gitlab/user_extractor_spec.rb
new file mode 100644
index 00000000000..fcc05ab3a0c
--- /dev/null
+++ b/spec/lib/gitlab/user_extractor_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UserExtractor do
+ let(:text) do
+ <<~TXT
+ This is a long texth that mentions some users.
+ @user-1, @user-2 and user@gitlab.org take a walk in the park.
+ There they meet @user-4 that was out with other-user@gitlab.org.
+ @user-1 thought it was late, so went home straight away
+ TXT
+ end
+ subject(:extractor) { described_class.new(text) }
+
+ describe '#users' do
+ it 'returns an empty relation when nil was passed' do
+ extractor = described_class.new(nil)
+
+ expect(extractor.users).to be_empty
+ expect(extractor.users).to be_a(ActiveRecord::Relation)
+ end
+
+ it 'returns the user case insensitive for usernames' do
+ user = create(:user, username: "USER-4")
+
+ expect(extractor.users).to include(user)
+ end
+
+ it 'returns users by primary email' do
+ user = create(:user, email: 'user@gitlab.org')
+
+ expect(extractor.users).to include(user)
+ end
+
+ it 'returns users by secondary email' do
+ user = create(:email, email: 'other-user@gitlab.org').user
+
+ expect(extractor.users).to include(user)
+ end
+ end
+
+ describe '#matches' do
+ it 'includes all mentioned email adresses' do
+ expect(extractor.matches[:emails]).to contain_exactly('user@gitlab.org', 'other-user@gitlab.org')
+ end
+
+ it 'includes all mentioned usernames' do
+ expect(extractor.matches[:usernames]).to contain_exactly('user-1', 'user-2', 'user-4')
+ end
+ end
+
+ describe '#references' do
+ it 'includes all user-references once' do
+ expect(extractor.references).to contain_exactly('user-1', 'user-2', 'user@gitlab.org', 'user-4', 'other-user@gitlab.org')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index c8a1e433d59..30035c79e58 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -57,6 +57,9 @@ describe 'Gitlab::VersionInfo' do
context 'parse' do
it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) }
+ it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) }
+ it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1")).to eq(@v1_0_0) }
+ it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid }
end
diff --git a/spec/lib/gitlab/web_ide_commits_counter_spec.rb b/spec/lib/gitlab/web_ide_commits_counter_spec.rb
new file mode 100644
index 00000000000..c51889a1c63
--- /dev/null
+++ b/spec/lib/gitlab/web_ide_commits_counter_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::WebIdeCommitsCounter, :clean_gitlab_redis_shared_state do
+ describe '.increment' do
+ it 'increments the web ide commits counter by 1' do
+ expect do
+ described_class.increment
+ end.to change { described_class.total_count }.from(0).to(1)
+ end
+ end
+
+ describe '.total_count' do
+ it 'returns the total amount of web ide commits' do
+ expect(described_class.total_count).to eq(0)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 23869f3d2da..b3f55a2e1bd 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -336,6 +336,22 @@ describe Gitlab::Workhorse do
it { expect { subject }.to raise_exception('Unsupported action: download') }
end
end
+
+ context 'when receive_max_input_size has been updated' do
+ it 'returns custom git config' do
+ allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { 1 }
+
+ expect(subject[:GitConfigOptions]).to be_present
+ end
+ end
+
+ context 'when receive_max_input_size is empty' do
+ it 'returns an empty git config' do
+ allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { nil }
+
+ expect(subject[:GitConfigOptions]).to be_empty
+ end
+ end
end
describe '.set_key_and_notify' do
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index 27cb3198e5b..e2134dc279c 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -66,25 +66,30 @@ describe GoogleApi::CloudPlatform::Client do
describe '#projects_zones_clusters_create' do
subject do
client.projects_zones_clusters_create(
- spy, spy, cluster_name, cluster_size, machine_type: machine_type)
+ project_id, zone, cluster_name, cluster_size, machine_type: machine_type, legacy_abac: legacy_abac)
end
+ let(:project_id) { 'project-123' }
+ let(:zone) { 'us-central1-a' }
let(:cluster_name) { 'test-cluster' }
let(:cluster_size) { 1 }
let(:machine_type) { 'n1-standard-2' }
+ let(:legacy_abac) { true }
+ let(:create_cluster_request_body) { double('Google::Apis::ContainerV1::CreateClusterRequest') }
let(:operation) { double }
before do
allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
- .to receive(:create_cluster).with(any_args, options: user_agent_options)
+ .to receive(:create_cluster).with(any_args)
.and_return(operation)
end
- it { is_expected.to eq(operation) }
-
it 'sets corresponded parameters' do
- expect_any_instance_of(Google::Apis::ContainerV1::CreateClusterRequest)
- .to receive(:initialize).with(
+ expect_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ .to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options)
+
+ expect(Google::Apis::ContainerV1::CreateClusterRequest)
+ .to receive(:new).with(
{
"cluster": {
"name": cluster_name,
@@ -96,9 +101,35 @@ describe GoogleApi::CloudPlatform::Client do
"enabled": true
}
}
- } )
+ } ).and_return(create_cluster_request_body)
+
+ expect(subject).to eq operation
+ end
+
+ context 'create without legacy_abac' do
+ let(:legacy_abac) { false }
+
+ it 'sets corresponded parameters' do
+ expect_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ .to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options)
+
+ expect(Google::Apis::ContainerV1::CreateClusterRequest)
+ .to receive(:new).with(
+ {
+ "cluster": {
+ "name": cluster_name,
+ "initial_node_count": cluster_size,
+ "node_config": {
+ "machine_type": machine_type
+ },
+ "legacy_abac": {
+ "enabled": false
+ }
+ }
+ } ).and_return(create_cluster_request_body)
- subject
+ expect(subject).to eq operation
+ end
end
end
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index b7687d48c68..f18f97a9c6a 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -82,7 +82,7 @@ describe Mattermost::Session, type: :request do
.to_return(headers: { Authorization: 'token thisworksnow' }, status: 200)
end
- it 'can setup a session' do
+ it 'can set up a session' do
subject.with_session do |session|
end
@@ -106,7 +106,7 @@ describe Mattermost::Session, type: :request do
expect_to_obtain_exclusive_lease(lease_key, 'uuid')
expect_to_cancel_exclusive_lease(lease_key, 'uuid')
- # Cannot setup a session, but we should still cancel the lease
+ # Cannot set up a session, but we should still cancel the lease
expect { subject.with_session }.to raise_error(Mattermost::NoSessionError)
end
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
index e0569218d78..1024e1a25ea 100644
--- a/spec/lib/object_storage/direct_upload_spec.rb
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -61,6 +61,8 @@ describe ObjectStorage::DirectUpload do
expect(subject[:GetURL]).to start_with(storage_url)
expect(subject[:StoreURL]).to start_with(storage_url)
expect(subject[:DeleteURL]).to start_with(storage_url)
+ expect(subject[:CustomPutHeaders]).to be_truthy
+ expect(subject[:PutHeaders]).to eq({})
end
end
@@ -81,6 +83,16 @@ describe ObjectStorage::DirectUpload do
expect(subject[:MultipartUpload][:AbortURL]).to start_with(storage_url)
expect(subject[:MultipartUpload][:AbortURL]).to include('uploadId=myUpload')
end
+
+ it 'uses only strings in query parameters' do
+ expect(direct_upload.send(:connection)).to receive(:signed_url).at_least(:once) do |params|
+ if params[:query]
+ expect(params[:query].keys.all? { |key| key.is_a?(String) }).to be_truthy
+ end
+ end
+
+ subject
+ end
end
shared_examples 'a valid upload without multipart data' do
diff --git a/spec/lib/quality/helm_client_spec.rb b/spec/lib/quality/helm_client_spec.rb
new file mode 100644
index 00000000000..553cd8719de
--- /dev/null
+++ b/spec/lib/quality/helm_client_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Quality::HelmClient do
+ let(:namespace) { 'review-apps-ee' }
+ let(:release_name) { 'my-release' }
+ let(:raw_helm_list_result) do
+ <<~OUTPUT
+ NAME REVISION UPDATED STATUS CHART NAMESPACE
+ review-improve-re-2dsd9d 1 Tue Jul 31 15:53:17 2018 FAILED gitlab-0.3.4 #{namespace}
+ review-11-1-stabl-3r2fso 1 Mon Jul 30 22:44:14 2018 FAILED gitlab-0.3.3 #{namespace}
+ review-49375-css-fk664j 1 Thu Jul 19 11:01:30 2018 FAILED gitlab-0.2.4 #{namespace}
+ OUTPUT
+ end
+
+ subject { described_class.new(namespace: namespace) }
+
+ describe '#releases' do
+ it 'calls helm list with default arguments' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm list --namespace "#{namespace}")])
+ .and_return(Gitlab::Popen::Result.new([], ''))
+
+ subject.releases
+ end
+
+ it 'calls helm list with given arguments' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm list --namespace "#{namespace}" --deployed)])
+ .and_return(Gitlab::Popen::Result.new([], ''))
+
+ subject.releases(args: ['--deployed'])
+ end
+
+ it 'returns a list of Release objects' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with([%(helm list --namespace "#{namespace}" --deployed)])
+ .and_return(Gitlab::Popen::Result.new([], raw_helm_list_result))
+
+ releases = subject.releases(args: ['--deployed'])
+
+ expect(releases.size).to eq(3)
+ expect(releases[0].name).to eq('review-improve-re-2dsd9d')
+ expect(releases[0].revision).to eq(1)
+ expect(releases[0].last_update).to eq(Time.parse('Tue Jul 31 15:53:17 2018'))
+ expect(releases[0].status).to eq('FAILED')
+ expect(releases[0].chart).to eq('gitlab-0.3.4')
+ expect(releases[0].namespace).to eq(namespace)
+ end
+ end
+
+ describe '#delete' do
+ it 'calls helm delete with default arguments' do
+ expect(Gitlab::Popen).to receive(:popen_with_detail)
+ .with(["helm delete --purge #{release_name}"])
+ .and_return(Gitlab::Popen::Result.new([], '', '', 0))
+
+ expect(subject.delete(release_name: release_name).status).to eq(0)
+ end
+ end
+end
diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb
new file mode 100644
index 00000000000..3c0c0d0977a
--- /dev/null
+++ b/spec/lib/quality/kubernetes_client_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Quality::KubernetesClient do
+ subject { described_class.new(namespace: 'review-apps-ee') }
+
+ describe '#cleanup' do
+ it 'calls kubectl with the correct arguments' do
+ # popen_with_detail will receive an array with a bunch of arguments; we're
+ # only concerned with it having the correct namespace and release name
+ expect(Gitlab::Popen).to receive(:popen_with_detail) do |args|
+ expect(args)
+ .to satisfy_one { |arg| arg.start_with?('-n "review-apps-ee" get') }
+ expect(args)
+ .to satisfy_one { |arg| arg == 'grep "my-release"' }
+ expect(args)
+ .to satisfy_one { |arg| arg.end_with?('-n "review-apps-ee" delete') }
+ end
+
+ # We're not verifying the output here, just silencing it
+ expect { subject.cleanup(release_name: 'my-release') }.to output.to_stdout
+ end
+ end
+end