summaryrefslogtreecommitdiff
path: root/spec/lib
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2016-11-10 15:16:33 +0000
committerLin Jen-Shin <godfat@godfat.org>2016-11-10 15:16:33 +0000
commit42e252da421bd11fd249897d7e7315c18910f0e9 (patch)
treec34e9b7a6a5dcd3a43b4e3aae347b7832a4b331a /spec/lib
parentc3508851bff289fdaaa114298b3ae13513646775 (diff)
parent87cc458a22e0cf91ca5ffe5b988077ec41e59404 (diff)
downloadgitlab-ce-42e252da421bd11fd249897d7e7315c18910f0e9.tar.gz
Merge remote-tracking branch 'upstream/master' into feature/1376-allow-write-access-deploy-keys
* upstream/master: (3852 commits) Grapify token API Fix cache for commit status in commits list to respect branches Grapify milestones API Grapify runners API Improve EeCompatCheck, cache EE repo and keep artifacts for the ee_compat_check task Use 'Forking in progress' title when appropriate Fix CHANGELOG after 8.14.0-rc1 tag Update CHANGELOG.md for 8.14.0-rc1 Fix YAML syntax on CHANGELOG entry Remove redundant rescue from repository keep_around Remove redundant space from repository model code Remove order-dependent expectation Minor CHANGELOG.md cleanups Add a link to Git cheatsheet PDF in docs readme Grapify the session API Add 8.13.5, 8.12.9, and 8.11.11 CHANGELOG Merge branch 'unauthenticated-container-registry-access' into 'security' Merge branch '23403-fix-events-for-private-project-features' into 'security' Merge branch 'fix-unathorized-cloning' into 'security' Merge branch 'markdown-xss-fix-option-2.1' into 'security' ...
Diffstat (limited to 'spec/lib')
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb22
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb85
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb74
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb34
-rw-r--r--spec/lib/banzai/filter/html_entity_filter_spec.rb19
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb59
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb84
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/redactor_filter_spec.rb42
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb40
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb8
-rw-r--r--spec/lib/banzai/filter/task_list_filter_spec.rb16
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb17
-rw-r--r--spec/lib/banzai/note_renderer_spec.rb3
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb66
-rw-r--r--spec/lib/banzai/pipeline/description_pipeline_spec.rb12
-rw-r--r--spec/lib/banzai/pipeline/full_pipeline_spec.rb28
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb7
-rw-r--r--spec/lib/banzai/redactor_spec.rb75
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb37
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb8
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb8
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb8
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb10
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb8
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb13
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb8
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb8
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb12
-rw-r--r--spec/lib/banzai/renderer_spec.rb74
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb102
-rw-r--r--spec/lib/ci/mask_secret_spec.rb27
-rw-r--r--spec/lib/constraints/constrainer_helper_spec.rb20
-rw-r--r--spec/lib/constraints/group_url_constrainer_spec.rb19
-rw-r--r--spec/lib/constraints/user_url_constrainer_spec.rb16
-rw-r--r--spec/lib/event_filter_spec.rb49
-rw-r--r--spec/lib/expand_variables_spec.rb73
-rw-r--r--spec/lib/extracts_path_spec.rb116
-rw-r--r--spec/lib/gitlab/auth_spec.rb97
-rw-r--r--spec/lib/gitlab/backend/shell_spec.rb42
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb67
-rw-r--r--spec/lib/gitlab/ci/config/node/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/node/environment_spec.rb217
-rw-r--r--spec/lib/gitlab/ci/config/node/factory_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/config/node/global_spec.rb81
-rw-r--r--spec/lib/gitlab/ci/config/node/hidden_spec.rb (renamed from spec/lib/gitlab/ci/config/node/hidden_job_spec.rb)17
-rw-r--r--spec/lib/gitlab/ci/config/node/job_spec.rb88
-rw-r--r--spec/lib/gitlab/ci/config/node/jobs_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/node/null_spec.rb41
-rw-r--r--spec/lib/gitlab/ci/config/node/script_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/node/undefined_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/config/node/unspecified_spec.rb32
-rw-r--r--spec/lib/gitlab/ci/pipeline_duration_spec.rb115
-rw-r--r--spec/lib/gitlab/ci/trace_reader_spec.rb40
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb3
-rw-r--r--spec/lib/gitlab/conflict/file_collection_spec.rb24
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb272
-rw-r--r--spec/lib/gitlab/conflict/parser_spec.rb193
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb104
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb8
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb99
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb42
-rw-r--r--spec/lib/gitlab/downtime_check/message_spec.rb26
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb6
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb79
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb56
-rw-r--r--spec/lib/gitlab/gfm/reference_rewriter_spec.rb28
-rw-r--r--spec/lib/gitlab/git_access_spec.rb162
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb11
-rw-r--r--spec/lib/gitlab/git_spec.rb45
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/comment_formatter_spec.rb6
-rw-r--r--spec/lib/gitlab/github_import/importer_spec.rb173
-rw-r--r--spec/lib/gitlab/github_import/issue_formatter_spec.rb11
-rw-r--r--spec/lib/gitlab/github_import/label_formatter_spec.rb28
-rw-r--r--spec/lib/gitlab/github_import/milestone_formatter_spec.rb5
-rw-r--r--spec/lib/gitlab/github_import/project_creator_spec.rb74
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb24
-rw-r--r--spec/lib/gitlab/github_import/release_formatter_spec.rb54
-rw-r--r--spec/lib/gitlab/gitlab_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/gitorious_import/project_creator_spec.rb26
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb7
-rw-r--r--spec/lib/gitlab/identifier_spec.rb123
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml191
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb37
-rw-r--r--spec/lib/gitlab/import_export/attribute_configuration_spec.rb56
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb42
-rw-r--r--spec/lib/gitlab/import_export/model_configuration_spec.rb57
-rw-r--r--spec/lib/gitlab/import_export/project.json171
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb102
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb40
-rw-r--r--spec/lib/gitlab/import_export/reader_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb125
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml342
-rw-r--r--spec/lib/gitlab/import_export/version_checker_spec.rb18
-rw-r--r--spec/lib/gitlab/ldap/adapter_spec.rb108
-rw-r--r--spec/lib/gitlab/ldap/config_spec.rb39
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb51
-rw-r--r--spec/lib/gitlab/metrics/metric_spec.rb18
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb30
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb24
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb57
-rw-r--r--spec/lib/gitlab/metrics_spec.rb24
-rw-r--r--spec/lib/gitlab/middleware/rails_queue_duration_spec.rb2
-rw-r--r--spec/lib/gitlab/optimistic_locking_spec.rb39
-rw-r--r--spec/lib/gitlab/popen_spec.rb9
-rw-r--r--spec/lib/gitlab/redis_spec.rb135
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb3
-rw-r--r--spec/lib/gitlab/search_results_spec.rb18
-rw-r--r--spec/lib/gitlab/slash_commands/command_definition_spec.rb173
-rw-r--r--spec/lib/gitlab/slash_commands/dsl_spec.rb77
-rw-r--r--spec/lib/gitlab/slash_commands/extractor_spec.rb215
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb6
-rw-r--r--spec/lib/gitlab/template/issue_template_spec.rb6
-rw-r--r--spec/lib/gitlab/template/merge_request_template_spec.rb6
-rw-r--r--spec/lib/gitlab/utils_spec.rb35
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb131
121 files changed, 5677 insertions, 625 deletions
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index dca7f997570..a6d2ea11fcc 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -99,6 +99,28 @@ describe Banzai::Filter::AutolinkFilter, lib: true do
expect(doc.at_css('a')['href']).to eq link
end
+ it 'autolinks rdar' do
+ link = 'rdar://localhost.com/blah'
+ doc = filter("See #{link}")
+
+ expect(doc.at_css('a').text).to eq link
+ expect(doc.at_css('a')['href']).to eq link
+ end
+
+ it 'does not autolink javascript' do
+ link = 'javascript://alert(document.cookie);'
+ doc = filter("See #{link}")
+
+ expect(doc.at_css('a')).to be_nil
+ end
+
+ it 'does not autolink bad URLs' do
+ link = 'foo://23423:::asdf'
+ doc = filter("See #{link}")
+
+ expect(doc.to_s).to eq("See #{link}")
+ end
+
it 'does not include trailing punctuation' do
doc = filter("See #{link}.")
expect(doc.at_css('a').text).to eq link
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 593bd6d5cac..e6c90ad87ee 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -65,14 +65,14 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to eq exp
end
- it 'includes a title attribute' do
+ it 'includes no title attribute' do
doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.attr('title')).to eq range.reference_title
+ expect(doc.css('a').first.attr('title')).to eq ""
end
it 'includes default classes' do
doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range has-tooltip'
end
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index d46d3f1489e..e0f08282551 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -55,7 +55,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
it 'includes a title attribute' do
doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.attr('title')).to eq commit.link_title
+ expect(doc.css('a').first.attr('title')).to eq commit.title
end
it 'escapes the title attribute' do
@@ -67,7 +67,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
it 'includes default classes' do
doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit has-tooltip'
end
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index b5b38cf0c8c..c8e62f528df 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -12,11 +12,16 @@ describe Banzai::Filter::EmojiFilter, lib: true do
ActionController::Base.asset_host = @original_asset_host
end
- it 'replaces supported emoji' do
+ it 'replaces supported name emoji' do
doc = filter('<p>:heart:</p>')
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png'
end
+ it 'replaces supported unicode emoji' do
+ doc = filter('<p>❤️</p>')
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png'
+ end
+
it 'ignores unsupported emoji' do
exp = act = '<p>:foo:</p>'
doc = filter(act)
@@ -28,46 +33,96 @@ describe Banzai::Filter::EmojiFilter, lib: true do
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png'
end
+ it 'correctly encodes unicode to the URL' do
+ doc = filter('<p>👍</p>')
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png'
+ end
+
it 'matches at the start of a string' do
doc = filter(':+1:')
expect(doc.css('img').size).to eq 1
end
+ it 'unicode matches at the start of a string' do
+ doc = filter("'👍'")
+ expect(doc.css('img').size).to eq 1
+ end
+
it 'matches at the end of a string' do
doc = filter('This gets a :-1:')
expect(doc.css('img').size).to eq 1
end
+ it 'unicode matches at the end of a string' do
+ doc = filter('This gets a 👍')
+ expect(doc.css('img').size).to eq 1
+ end
+
it 'matches with adjacent text' do
doc = filter('+1 (:+1:)')
expect(doc.css('img').size).to eq 1
end
+ it 'unicode matches with adjacent text' do
+ doc = filter('+1 (👍)')
+ expect(doc.css('img').size).to eq 1
+ end
+
it 'matches multiple emoji in a row' do
doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
expect(doc.css('img').size).to eq 3
end
+ it 'unicode matches multiple emoji in a row' do
+ doc = filter("'🙈🙉🙊'")
+ expect(doc.css('img').size).to eq 3
+ end
+
+ it 'mixed matches multiple emoji in a row' do
+ doc = filter("'🙈:see_no_evil:🙉:hear_no_evil:🙊:speak_no_evil:'")
+ expect(doc.css('img').size).to eq 6
+ end
+
it 'has a title attribute' do
doc = filter(':-1:')
expect(doc.css('img').first.attr('title')).to eq ':-1:'
end
+ it 'unicode has a title attribute' do
+ doc = filter("'👎'")
+ expect(doc.css('img').first.attr('title')).to eq ':thumbsdown:'
+ end
+
it 'has an alt attribute' do
doc = filter(':-1:')
expect(doc.css('img').first.attr('alt')).to eq ':-1:'
end
+ it 'unicode has an alt attribute' do
+ doc = filter("'👎'")
+ expect(doc.css('img').first.attr('alt')).to eq ':thumbsdown:'
+ end
+
it 'has an align attribute' do
doc = filter(':8ball:')
expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
end
+ it 'unicode has an align attribute' do
+ doc = filter("'🎱'")
+ expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
+ end
+
it 'has an emoji class' do
doc = filter(':cat:')
expect(doc.css('img').first.attr('class')).to eq 'emoji'
end
+ it 'unicode has an emoji class' do
+ doc = filter("'🐱'")
+ expect(doc.css('img').first.attr('class')).to eq 'emoji'
+ end
+
it 'has height and width attributes' do
doc = filter(':dog:')
img = doc.css('img').first
@@ -76,12 +131,26 @@ describe Banzai::Filter::EmojiFilter, lib: true do
expect(img.attr('height')).to eq '20'
end
+ it 'unicode has height and width attributes' do
+ doc = filter("'🐶'")
+ img = doc.css('img').first
+
+ expect(img.attr('width')).to eq '20'
+ expect(img.attr('height')).to eq '20'
+ end
+
it 'keeps whitespace intact' do
doc = filter('This deserves a :+1:, big time.')
expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
end
+ it 'unicode keeps whitespace intact' do
+ doc = filter('This deserves a 🎱, big time.')
+
+ expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
+ end
+
it 'uses a custom asset_root context' do
root = Gitlab.config.gitlab.url + 'gitlab/root'
@@ -95,4 +164,18 @@ describe Banzai::Filter::EmojiFilter, lib: true do
doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
end
+
+ it 'uses a custom asset_root context' do
+ root = Gitlab.config.gitlab.url + 'gitlab/root'
+
+ doc = filter("'🎱'", asset_root: root)
+ expect(doc.css('img').first.attr('src')).to start_with(root)
+ end
+
+ it 'uses a custom asset_host context' do
+ ActionController::Base.asset_host = 'https://cdn.example.com'
+
+ doc = filter("'🎱'", asset_host: 'https://this-is-ignored-i-guess?')
+ expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
+ end
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 953466679e4..fbf7a461fa5 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -7,11 +7,8 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
IssuesHelper
end
- let(:project) { create(:jira_project) }
-
- context 'JIRA issue references' do
- let(:issue) { ExternalIssue.new('JIRA-123', project) }
- let(:reference) { issue.to_reference }
+ shared_examples_for "external issue tracker" do
+ it_behaves_like 'a reference containing an element node'
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
@@ -20,6 +17,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
+
expect(filter(act).to_html).to eq exp
end
end
@@ -33,25 +31,30 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
it 'links to a valid reference' do
doc = filter("Issue #{reference}")
+ issue_id = doc.css('a').first.attr("data-external-issue")
+
expect(doc.css('a').first.attr('href'))
- .to eq helper.url_for_issue(reference, project)
+ .to eq helper.url_for_issue(issue_id, project)
end
it 'links to the external tracker' do
doc = filter("Issue #{reference}")
+
link = doc.css('a').first.attr('href')
+ issue_id = doc.css('a').first.attr("data-external-issue")
- expect(link).to eq "http://jira.example/browse/#{reference}"
+ expect(link).to eq(helper.url_for_issue(issue_id, project))
end
it 'links with adjacent text' do
doc = filter("Issue (#{reference}.)")
+
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
end
it 'includes a title attribute' do
doc = filter("Issue #{reference}")
- expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
+ expect(doc.css('a').first.attr('title')).to include("Issue in #{project.issues_tracker.title}")
end
it 'escapes the title attribute' do
@@ -64,14 +67,65 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
it 'includes default classes' do
doc = filter("Issue #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
end
it 'supports an :only_path context' do
doc = filter("Issue #{reference}", only_path: true)
+
link = doc.css('a').first.attr('href')
+ issue_id = doc.css('a').first["data-external-issue"]
+
+ expect(link).to eq helper.url_for_issue(issue_id, project, only_path: true)
+ end
+
+ context 'with RequestStore enabled' do
+ let(:reference_filter) { HTML::Pipeline.new([described_class]) }
+
+ before { allow(RequestStore).to receive(:active?).and_return(true) }
+
+ 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(:issue_reference_pattern).once.and_call_original
- expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
+ not_cached = reference_filter.call("look for #{reference}", { project: project })
+
+ expect_any_instance_of(Project).not_to receive(:default_issues_tracker?)
+ expect_any_instance_of(Project).not_to receive(:issue_reference_pattern)
+
+ cached = reference_filter.call("look for #{reference}", { project: project })
+
+ # Links must be the same
+ expect(cached[:output].css('a').first[:href]).to eq(not_cached[:output].css('a').first[:href])
+ end
+ end
+ end
+
+ context "redmine project" do
+ let(:project) { create(:redmine_project) }
+ let(:issue) { ExternalIssue.new("#123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "jira project" do
+ let(:project) { create(:jira_project) }
+ let(:reference) { issue.to_reference }
+
+ context "with right markdown" do
+ let(:issue) { ExternalIssue.new("JIRA-123", project) }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with wrong markdown" do
+ let(:issue) { ExternalIssue.new("#123", project) }
+
+ it "ignores reference" do
+ exp = act = "Issue #{reference}"
+ expect(filter(act).to_html).to eq exp
+ end
end
end
end
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 695a5bc6fd4..167397c736b 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -46,4 +46,38 @@ describe Banzai::Filter::ExternalLinkFilter, lib: true do
expect(doc.at_css('a')['rel']).to include 'noreferrer'
end
end
+
+ context 'for non-lowercase scheme links' do
+ let(:doc_with_http) { filter %q(<p><a href="httP://google.com/">Google</a></p>) }
+ let(:doc_with_https) { filter %q(<p><a href="hTTpS://google.com/">Google</a></p>) }
+
+ it 'adds rel="nofollow" to external links' do
+ expect(doc_with_http.at_css('a')).to have_attribute('rel')
+ expect(doc_with_https.at_css('a')).to have_attribute('rel')
+
+ expect(doc_with_http.at_css('a')['rel']).to include 'nofollow'
+ expect(doc_with_https.at_css('a')['rel']).to include 'nofollow'
+ end
+
+ it 'adds rel="noreferrer" to external links' do
+ expect(doc_with_http.at_css('a')).to have_attribute('rel')
+ expect(doc_with_https.at_css('a')).to have_attribute('rel')
+
+ expect(doc_with_http.at_css('a')['rel']).to include 'noreferrer'
+ expect(doc_with_https.at_css('a')['rel']).to include 'noreferrer'
+ end
+
+ it 'skips internal links' do
+ internal_link = Gitlab.config.gitlab.url + "/sign_in"
+ url = internal_link.gsub(/\Ahttp/, 'HtTp')
+ act = %Q(<a href="#{url}">Login</a>)
+ exp = %Q(<a href="#{internal_link}">Login</a>)
+ expect(filter(act).to_html).to eq(exp)
+ end
+
+ it 'skips relative links' do
+ exp = act = %q(<a href="http_spec/foo.rb">Relative URL</a>)
+ expect(filter(act).to_html).to eq(exp)
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb
new file mode 100644
index 00000000000..f9e6bd609f0
--- /dev/null
+++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Banzai::Filter::HtmlEntityFilter, lib: true do
+ include FilterSpecHelper
+
+ let(:unescaped) { 'foo <strike attr="foo">&&&</strike>' }
+ let(:escaped) { 'foo &lt;strike attr=&quot;foo&quot;&gt;&amp;&amp;&amp;&lt;/strike&gt;' }
+
+ it 'converts common entities to their HTML-escaped equivalents' do
+ output = filter(unescaped)
+
+ expect(output).to eq(escaped)
+ end
+
+ it 'does not double-escape' do
+ escaped = ERB::Util.html_escape("Merge branch 'blabla' into 'master'")
+ expect(filter(escaped)).to eq(escaped)
+ end
+end
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index a005b4990e7..8f0b2db3e8e 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -22,12 +22,12 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
end
context 'internal reference' do
+ it_behaves_like 'a reference containing an element node'
+
let(:reference) { issue.to_reference }
it 'ignores valid references when using non-default tracker' do
- expect_any_instance_of(described_class).to receive(:find_object).
- with(project, issue.iid).
- and_return(nil)
+ allow(project).to receive(:default_issues_tracker?).and_return(false)
exp = act = "Issue #{reference}"
expect(reference_filter(act).to_html).to eq exp
@@ -54,7 +54,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
it 'includes a title attribute' do
doc = reference_filter("Issue #{reference}")
- expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
+ expect(doc.css('a').first.attr('title')).to eq issue.title
end
it 'escapes the title attribute' do
@@ -66,7 +66,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
it 'includes default classes' do
doc = reference_filter("Issue #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
end
it 'includes a data-project attribute' do
@@ -85,6 +85,20 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
expect(link.attr('data-issue')).to eq issue.id.to_s
end
+ it 'includes a data-original attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-original')
+ expect(link.attr('data-original')).to eq reference
+ end
+
+ it 'does not escape the data-original attribute' do
+ inner_html = 'element <code>node</code> inside'
+ doc = reference_filter(%{<a href="#{reference}">#{inner_html}</a>})
+ expect(doc.children.first.attr('data-original')).to eq inner_html
+ end
+
it 'supports an :only_path context' do
doc = reference_filter("Issue #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
@@ -103,6 +117,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
end
context 'cross-project reference' do
+ it_behaves_like 'a reference containing an element node'
+
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
@@ -143,6 +159,8 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
end
context 'cross-project URL reference' do
+ it_behaves_like 'a reference containing an element node'
+
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
@@ -162,56 +180,49 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
end
context 'cross-project reference in link href' do
+ it_behaves_like 'a reference containing an element node'
+
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
- let(:reference) { %Q{<a href="#{issue.to_reference(project)}">Reference</a>} }
+ let(:reference) { issue.to_reference(project) }
+ let(:reference_link) { %{<a href="#{reference}">Reference</a>} }
it 'links to a valid reference' do
- doc = reference_filter("See #{reference}")
+ doc = reference_filter("See #{reference_link}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
it 'links with adjacent text' do
- doc = reference_filter("Fixed (#{reference}.)")
+ doc = reference_filter("Fixed (#{reference_link}.)")
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end
end
context 'cross-project URL in link href' do
+ it_behaves_like 'a reference containing an element node'
+
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
- let(:reference) { %Q{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} }
+ let(:reference) { "#{helper.url_for_issue(issue.iid, project2) + "#note_123"}" }
+ let(:reference_link) { %{<a href="#{reference}">Reference</a>} }
it 'links to a valid reference' do
- doc = reference_filter("See #{reference}")
+ doc = reference_filter("See #{reference_link}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2) + "#note_123"
end
it 'links with adjacent text' do
- doc = reference_filter("Fixed (#{reference}.)")
+ doc = reference_filter("Fixed (#{reference_link}.)")
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end
end
- context 'referencing external issues' do
- let(:project) { create(:redmine_project) }
-
- it 'renders internal issue IDs as external issue links' do
- doc = reference_filter('#1')
- link = doc.css('a').first
-
- expect(link.attr('data-reference-type')).to eq('external_issue')
- expect(link.attr('title')).to eq('Issue in Redmine')
- expect(link.attr('data-external-issue')).to eq('1')
- end
- end
-
describe '#issues_per_Project' do
context 'using an internal issue tracker' do
it 'returns a Hash containing the issues per project' do
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 9276a154007..9c09f00ae8a 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -21,7 +21,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'includes default classes' do
doc = reference_filter("Label #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label has-tooltip'
end
it 'includes a data-project attribute' do
@@ -305,6 +305,58 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
end
+ describe 'group label references' do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, :public, namespace: group) }
+ let(:group_label) { create(:group_label, name: 'gfm references', group: group) }
+
+ context 'without project reference' do
+ let(:reference) { group_label.to_reference(format: :name) }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}", project: project)
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: group_label.name)
+ expect(doc.text).to eq 'See gfm references'
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Label (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>#{group_label.name}</span></a>\.\)))
+ end
+
+ it 'ignores invalid label names' do
+ exp = act = %(Label #{Label.reference_prefix}"#{group_label.name.reverse}")
+
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'with project reference' do
+ let(:reference) { project.to_reference + group_label.to_reference(format: :name) }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}", project: project)
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: group_label.name)
+ expect(doc.text).to eq 'See gfm references'
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Label (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>#{group_label.name}</span></a>\.\)))
+ end
+
+ it 'ignores invalid label names' do
+ exp = act = %(Label #{project.to_reference}#{Label.reference_prefix}"#{group_label.name.reverse}")
+
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+ end
+
describe 'cross project label references' do
context 'valid project referenced' do
let(:another_project) { create(:empty_project, :public) }
@@ -339,4 +391,34 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
end
end
+
+ describe 'cross group label references' do
+ context 'valid project referenced' do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, :public, namespace: group) }
+ let(:another_group) { create(:group) }
+ let(:another_project) { create(:empty_project, :public, namespace: another_group) }
+ let(:project_name) { another_project.name_with_namespace }
+ let(:group_label) { create(:group_label, group: another_group, color: '#00ff00') }
+ let(:reference) { another_project.to_reference + group_label.to_reference }
+
+ let!(:result) { reference_filter("See #{reference}", project: project) }
+
+ it 'points to referenced project issues page' do
+ expect(result.css('a').first.attr('href'))
+ .to eq urls.namespace_project_issues_url(another_project.namespace,
+ another_project,
+ label_name: group_label.name)
+ end
+
+ it 'has valid color' do
+ expect(result.css('a span').first.attr('style'))
+ .to match /background-color: #00ff00/
+ end
+
+ it 'contains cross project content' do
+ expect(result.css('a').first.text).to eq "#{group_label.name} in #{project_name}"
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 805acf1c8b3..274258a045c 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -46,7 +46,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
it 'includes a title attribute' do
doc = reference_filter("Merge #{reference}")
- expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
+ expect(doc.css('a').first.attr('title')).to eq merge.title
end
it 'escapes the title attribute' do
@@ -58,7 +58,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
it 'includes default classes' do
doc = reference_filter("Merge #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request has-tooltip'
end
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 9424f2363e1..7419863d848 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -20,7 +20,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
it 'includes default classes' do
doc = reference_filter("Milestone #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip'
end
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb
index f181125156b..0140a91c7ba 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/redactor_filter_spec.rb
@@ -28,31 +28,39 @@ describe Banzai::Filter::RedactorFilter, lib: true do
and_return(parser_class)
end
- it 'removes unpermitted Project references' do
- user = create(:user)
- project = create(:empty_project)
+ context 'valid projects' do
+ before { allow_any_instance_of(Banzai::ReferenceParser::BaseParser).to receive(:can_read_reference?).and_return(true) }
- link = reference_link(project: project.id, reference_type: 'test')
- doc = filter(link, current_user: user)
+ it 'allows permitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+ project.team << [user, :master]
+
+ link = reference_link(project: project.id, reference_type: 'test')
+ doc = filter(link, current_user: user)
- expect(doc.css('a').length).to eq 0
+ expect(doc.css('a').length).to eq 1
+ end
end
- it 'allows permitted Project references' do
- user = create(:user)
- project = create(:empty_project)
- project.team << [user, :master]
+ context 'invalid projects' do
+ before { allow_any_instance_of(Banzai::ReferenceParser::BaseParser).to receive(:can_read_reference?).and_return(false) }
- link = reference_link(project: project.id, reference_type: 'test')
- doc = filter(link, current_user: user)
+ it 'removes unpermitted references' do
+ user = create(:user)
+ project = create(:empty_project)
- expect(doc.css('a').length).to eq 1
- end
+ link = reference_link(project: project.id, reference_type: 'test')
+ doc = filter(link, current_user: user)
- it 'handles invalid Project references' do
- link = reference_link(project: 12345, reference_type: 'test')
+ expect(doc.css('a').length).to eq 0
+ end
+
+ it 'handles invalid references' do
+ link = reference_link(project: 12345, reference_type: 'test')
- expect { filter(link) }.not_to raise_error
+ expect { filter(link) }.not_to raise_error
+ 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 6b58f3e43ee..2bfa51deb20 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -50,14 +50,6 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
end
end
- shared_examples :relative_to_requested do
- it 'rebuilds URL relative to the requested path' do
- doc = filter(link('users.md'))
- expect(doc.at_css('a')['href']).
- to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
- end
- end
-
context 'with a project_wiki' do
let(:project_wiki) { double('ProjectWiki') }
include_examples :preserve_unchanged
@@ -188,12 +180,38 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
context 'when requested path is a file in the repo' do
let(:requested_path) { 'doc/api/README.md' }
- include_examples :relative_to_requested
+ it 'rebuilds URL relative to the containing directory' do
+ doc = filter(link('users.md'))
+ expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md"
+ end
end
context 'when requested path is a directory in the repo' do
- let(:requested_path) { 'doc/api' }
- include_examples :relative_to_requested
+ let(:requested_path) { 'doc/api/' }
+ it 'rebuilds URL relative to the directory' do
+ doc = filter(link('users.md'))
+ expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md"
+ end
+ end
+
+ context 'when ref name contains percent sign' do
+ let(:ref) { '100%branch' }
+ let(:commit) { project.commit('1b12f15a11fc6e62177bef08f47bc7b5ce50b141') }
+ let(:requested_path) { 'foo/bar/' }
+ it 'correctly escapes the ref' do
+ doc = filter(link('.gitkeep'))
+ expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/foo/bar/.gitkeep"
+ end
+ end
+
+ context 'when requested path is a directory with space in the repo' do
+ let(:ref) { 'master' }
+ let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') }
+ let(:requested_path) { 'with space/' }
+ it 'does not escape the space twice' do
+ doc = filter(link('README.md'))
+ expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/with%20space/README.md"
+ end
end
end
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 5068ddd7faa..9b92d1a3926 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -39,7 +39,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
it 'includes a title attribute' do
doc = reference_filter("Snippet #{reference}")
- expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
+ expect(doc.css('a').first.attr('title')).to eq snippet.title
end
it 'escapes the title attribute' do
@@ -51,7 +51,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
it 'includes default classes' do
doc = reference_filter("Snippet #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet has-tooltip'
end
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index b1370bca833..d265d29ee86 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -6,21 +6,21 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
context "when no language is specified" do
it "highlights as plaintext" do
result = filter('<pre><code>def fun end</code></pre>')
- expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>def fun end</code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>def fun end</code></pre>')
end
end
context "when a valid language is specified" do
it "highlights as that language" do
result = filter('<pre><code class="ruby">def fun end</code></pre>')
- expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" v-pre="true"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>')
end
end
context "when an invalid language is specified" do
it "highlights as plaintext" do
result = filter('<pre><code class="gnuplot">This is a test</code></pre>')
- expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>This is a test</code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>This is a test</code></pre>')
end
end
@@ -31,7 +31,7 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
it "highlights as plaintext" do
result = filter('<pre><code class="ruby">This is a test</code></pre>')
- expect(result.to_html).to eq('<pre class="code highlight"><code>This is a test</code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight" v-pre="true"><code>This is a test</code></pre>')
end
end
end
diff --git a/spec/lib/banzai/filter/task_list_filter_spec.rb b/spec/lib/banzai/filter/task_list_filter_spec.rb
deleted file mode 100644
index 569cbc885c7..00000000000
--- a/spec/lib/banzai/filter/task_list_filter_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'spec_helper'
-
-describe Banzai::Filter::TaskListFilter, lib: true do
- include FilterSpecHelper
-
- it 'does not apply `task-list` class to non-task lists' do
- exp = act = %(<ul><li>Item</li></ul>)
- expect(filter(act).to_html).to eq exp
- end
-
- it 'applies `task-list` to single-item task lists' do
- act = filter('<ul><li>[ ] Task 1</li></ul>')
-
- expect(act.to_html).to start_with '<ul class="task-list">'
- end
-end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 108b36a97cc..5bfeb82e738 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -24,6 +24,8 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
end
context 'mentioning @all' do
+ it_behaves_like 'a reference containing an element node'
+
let(:reference) { User.reference_prefix + 'all' }
before do
@@ -31,13 +33,16 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
end
it 'supports a special @all mention' do
+ project.team << [user, :developer]
doc = reference_filter("Hey #{reference}", author: user)
+
expect(doc.css('a').length).to eq 1
expect(doc.css('a').first.attr('href'))
.to eq urls.namespace_project_url(project.namespace, project)
end
it 'includes a data-author attribute when there is an author' do
+ project.team << [user, :developer]
doc = reference_filter(reference, author: user)
expect(doc.css('a').first.attr('data-author')).to eq(user.id.to_s)
@@ -48,9 +53,17 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
expect(doc.css('a').first.has_attribute?('data-author')).to eq(false)
end
+
+ it 'ignores reference to all when the user is not a project member' do
+ doc = reference_filter("Hey #{reference}", author: user)
+
+ expect(doc.css('a').length).to eq 0
+ end
end
context 'mentioning a user' do
+ it_behaves_like 'a reference containing an element node'
+
it 'links to a User' do
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
@@ -80,6 +93,8 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
end
context 'mentioning a group' do
+ it_behaves_like 'a reference containing an element node'
+
let(:group) { create(:group) }
let(:reference) { group.to_reference }
@@ -104,7 +119,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
it 'includes default classes' do
doc = reference_filter("Hey #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip'
end
it 'supports an :only_path context' do
diff --git a/spec/lib/banzai/note_renderer_spec.rb b/spec/lib/banzai/note_renderer_spec.rb
index 98f76f36fd5..49556074278 100644
--- a/spec/lib/banzai/note_renderer_spec.rb
+++ b/spec/lib/banzai/note_renderer_spec.rb
@@ -12,8 +12,7 @@ describe Banzai::NoteRenderer do
with(project, user,
requested_path: 'foo',
project_wiki: wiki,
- ref: 'bar',
- pipeline: :note).
+ ref: 'bar').
and_call_original
expect_any_instance_of(Banzai::ObjectRenderer).
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index bcdb95250ca..6bcda87c999 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -4,10 +4,18 @@ describe Banzai::ObjectRenderer do
let(:project) { create(:empty_project) }
let(:user) { project.owner }
+ def fake_object(attrs = {})
+ object = double(attrs.merge("new_record?" => true, "destroyed?" => true))
+ allow(object).to receive(:markdown_cache_field_for).with(:note).and_return(:note_html)
+ allow(object).to receive(:banzai_render_context).with(:note).and_return(project: nil, author: nil)
+ allow(object).to receive(:update_column).with(:note_html, anything).and_return(true)
+ object
+ end
+
describe '#render' do
it 'renders and redacts an Array of objects' do
renderer = described_class.new(project, user)
- object = double(:object, note: 'hello', note_html: nil)
+ object = fake_object(note: 'hello', note_html: nil)
expect(renderer).to receive(:render_objects).with([object], :note).
and_call_original
@@ -16,7 +24,7 @@ describe Banzai::ObjectRenderer do
with(an_instance_of(Array)).
and_call_original
- expect(object).to receive(:note_html=).with('<p>hello</p>')
+ expect(object).to receive(:redacted_note_html=).with('<p dir="auto">hello</p>')
expect(object).to receive(:user_visible_reference_count=).with(0)
renderer.render([object], :note)
@@ -25,7 +33,7 @@ describe Banzai::ObjectRenderer do
describe '#render_objects' do
it 'renders an Array of objects' do
- object = double(:object, note: 'hello')
+ object = fake_object(note: 'hello', note_html: nil)
renderer = described_class.new(project, user)
@@ -57,74 +65,50 @@ describe Banzai::ObjectRenderer do
end
describe '#context_for' do
- let(:object) { double(:object, note: 'hello') }
+ let(:object) { fake_object(note: 'hello') }
let(:renderer) { described_class.new(project, user) }
it 'returns a Hash' do
expect(renderer.context_for(object, :note)).to be_an_instance_of(Hash)
end
- it 'includes the cache key' do
+ it 'includes the banzai render context for the object' do
+ expect(object).to receive(:banzai_render_context).with(:note).and_return(foo: :bar)
context = renderer.context_for(object, :note)
-
- expect(context[:cache_key]).to eq([object, :note])
- end
-
- context 'when the object responds to "author"' do
- it 'includes the author in the context' do
- expect(object).to receive(:author).and_return('Alice')
-
- context = renderer.context_for(object, :note)
-
- expect(context[:author]).to eq('Alice')
- end
- end
-
- context 'when the object does not respond to "author"' do
- it 'does not include the author in the context' do
- context = renderer.context_for(object, :note)
-
- expect(context.key?(:author)).to eq(false)
- end
+ expect(context).to have_key(:foo)
+ expect(context[:foo]).to eq(:bar)
end
end
describe '#render_attributes' do
it 'renders the attribute of a list of objects' do
- objects = [double(:doc, note: 'hello'), double(:doc, note: 'bye')]
- renderer = described_class.new(project, user, pipeline: :note)
+ objects = [fake_object(note: 'hello', note_html: nil), fake_object(note: 'bye', note_html: nil)]
+ renderer = described_class.new(project, user)
- expect(Banzai).to receive(:cache_collection_render).
- with([
- { text: 'hello', context: renderer.context_for(objects[0], :note) },
- { text: 'bye', context: renderer.context_for(objects[1], :note) }
- ]).
- and_call_original
+ objects.each do |object|
+ expect(Banzai).to receive(:render_field).with(object, :note).and_call_original
+ end
docs = renderer.render_attributes(objects, :note)
expect(docs[0]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment)
- expect(docs[0].to_html).to eq('<p>hello</p>')
+ expect(docs[0].to_html).to eq('<p dir="auto">hello</p>')
expect(docs[1]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment)
- expect(docs[1].to_html).to eq('<p>bye</p>')
+ expect(docs[1].to_html).to eq('<p dir="auto">bye</p>')
end
it 'returns when no objects to render' do
objects = []
renderer = described_class.new(project, user, pipeline: :note)
- expect(Banzai).to receive(:cache_collection_render).
- with([]).
- and_call_original
-
expect(renderer.render_attributes(objects, :note)).to eq([])
end
end
describe '#base_context' do
let(:context) do
- described_class.new(project, user, pipeline: :note).base_context
+ described_class.new(project, user, foo: :bar).base_context
end
it 'returns a Hash' do
@@ -132,7 +116,7 @@ describe Banzai::ObjectRenderer do
end
it 'includes the custom attributes' do
- expect(context[:pipeline]).to eq(:note)
+ expect(context[:foo]).to eq(:bar)
end
it 'includes the current user' do
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
index 76f42071810..8cce1b96698 100644
--- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -4,11 +4,11 @@ describe Banzai::Pipeline::DescriptionPipeline do
def parse(html)
# When we pass HTML to Redcarpet, it gets wrapped in `p` tags...
# ...except when we pass it pre-wrapped text. Rabble rabble.
- unwrap = !html.start_with?('<p>')
+ unwrap = !html.start_with?('<p ')
output = described_class.to_html(html, project: spy)
- output.gsub!(%r{\A<p>(.*)</p>(.*)\z}, '\1\2') if unwrap
+ output.gsub!(%r{\A<p dir="auto">(.*)</p>(.*)\z}, '\1\2') if unwrap
output
end
@@ -27,11 +27,17 @@ describe Banzai::Pipeline::DescriptionPipeline do
end
end
- %w(b i strong em a ins del sup sub p).each do |elem|
+ %w(b i strong em a ins del sup sub).each do |elem|
it "still allows '#{elem}' elements" do
exp = act = "<#{elem}>Description</#{elem}>"
expect(parse(act).strip).to eq exp
end
end
+
+ it "still allows 'p' elements" do
+ exp = act = "<p dir=\"auto\">Description</p>"
+
+ expect(parse(act).strip).to eq exp
+ end
end
diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
new file mode 100644
index 00000000000..2501b638774
--- /dev/null
+++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
@@ -0,0 +1,28 @@
+require 'rails_helper'
+
+describe Banzai::Pipeline::FullPipeline do
+ describe 'References' do
+ let(:project) { create(:empty_project, :public) }
+ let(:issue) { create(:issue, project: project) }
+
+ it 'handles markdown inside a reference' do
+ markdown = "[some `code` inside](#{issue.to_reference})"
+ result = described_class.call(markdown, project: project)
+ link_content = result[:output].css('a').inner_html
+ expect(link_content).to eq('some <code>code</code> inside')
+ end
+
+ it 'sanitizes reference HTML' do
+ link_label = '<script>bad things</script>'
+ markdown = "[#{link_label}](#{issue.to_reference})"
+ result = described_class.to_html(markdown, project: project)
+ expect(result).not_to include(link_label)
+ end
+
+ it 'escapes the data-original attribute on a reference' do
+ markdown = %Q{[">bad things](#{issue.to_reference})}
+ result = described_class.to_html(markdown, project: project)
+ expect(result).to include(%{data-original='\"&gt;bad things'})
+ end
+ end
+end
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 51c89ac4889..ac9bde6baf1 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -127,6 +127,13 @@ describe Banzai::Pipeline::WikiPipeline do
expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page.md\"")
end
+
+ it 'rewrites links 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-page#title\"")
+ end
end
describe "when creating root links" do
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb
index 254657a881d..6d2c141e18b 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/redactor_spec.rb
@@ -6,39 +6,60 @@ describe Banzai::Redactor do
let(:redactor) { described_class.new(project, user) }
describe '#redact' do
- it 'redacts an Array of documents' do
- doc1 = Nokogiri::HTML.
- fragment('<a class="gfm" data-reference-type="issue">foo</a>')
-
- doc2 = Nokogiri::HTML.
- fragment('<a class="gfm" data-reference-type="issue">bar</a>')
-
- expect(redactor).to receive(:nodes_visible_to_user).and_return([])
-
- redacted_data = redactor.redact([doc1, doc2])
-
- expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2])
- expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([0, 0])
- expect(doc1.to_html).to eq('foo')
- expect(doc2.to_html).to eq('bar')
+ context 'when reference not visible to user' do
+ before do
+ expect(redactor).to receive(:nodes_visible_to_user).and_return([])
+ end
+
+ it 'redacts an array of documents' do
+ doc1 = Nokogiri::HTML.
+ fragment('<a class="gfm" data-reference-type="issue">foo</a>')
+
+ doc2 = Nokogiri::HTML.
+ fragment('<a class="gfm" data-reference-type="issue">bar</a>')
+
+ redacted_data = redactor.redact([doc1, doc2])
+
+ expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2])
+ expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([0, 0])
+ expect(doc1.to_html).to eq('foo')
+ expect(doc2.to_html).to eq('bar')
+ end
+
+ it 'replaces redacted reference with inner HTML' do
+ doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue'>foo</a>")
+ redactor.redact([doc])
+ expect(doc.to_html).to eq('foo')
+ end
+
+ context 'when data-original attribute provided' do
+ let(:original_content) { '<code>foo</code>' }
+ it 'replaces redacted reference with original content' do
+ doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
+ redactor.redact([doc])
+ expect(doc.to_html).to eq(original_content)
+ end
+ end
end
- it 'does not redact an Array of documents' do
- doc1_html = '<a class="gfm" data-reference-type="issue">foo</a>'
- doc1 = Nokogiri::HTML.fragment(doc1_html)
+ context 'when reference visible to user' do
+ it 'does not redact an array of documents' do
+ doc1_html = '<a class="gfm" data-reference-type="issue">foo</a>'
+ doc1 = Nokogiri::HTML.fragment(doc1_html)
- doc2_html = '<a class="gfm" data-reference-type="issue">bar</a>'
- doc2 = Nokogiri::HTML.fragment(doc2_html)
+ doc2_html = '<a class="gfm" data-reference-type="issue">bar</a>'
+ doc2 = Nokogiri::HTML.fragment(doc2_html)
- nodes = redactor.document_nodes([doc1, doc2]).map { |x| x[:nodes] }
- expect(redactor).to receive(:nodes_visible_to_user).and_return(nodes.flatten)
+ nodes = redactor.document_nodes([doc1, doc2]).map { |x| x[:nodes] }
+ expect(redactor).to receive(:nodes_visible_to_user).and_return(nodes.flatten)
- redacted_data = redactor.redact([doc1, doc2])
+ redacted_data = redactor.redact([doc1, doc2])
- expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2])
- expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([1, 1])
- expect(doc1.to_html).to eq(doc1_html)
- expect(doc2.to_html).to eq(doc2_html)
+ expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2])
+ expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([1, 1])
+ expect(doc1.to_html).to eq(doc1_html)
+ expect(doc2.to_html).to eq(doc2_html)
+ 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 ac9c66e2663..aa127f0179d 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -27,41 +27,12 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
let(:link) { empty_html_link }
context 'when the link has a data-project attribute' do
- it 'returns the nodes if the attribute value equals the current project ID' do
+ it 'checks if user can read the resource' do
link['data-project'] = project.id.to_s
- expect(Ability.abilities).not_to receive(:allowed?)
- expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
- end
-
- it 'returns the nodes if the user can read the project' do
- other_project = create(:empty_project, :public)
-
- link['data-project'] = other_project.id.to_s
-
- expect(Ability.abilities).to receive(:allowed?).
- with(user, :read_project, other_project).
- and_return(true)
-
- expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
- end
-
- it 'returns an empty Array when the attribute value is empty' do
- link['data-project'] = ''
-
- expect(subject.nodes_visible_to_user(user, [link])).to eq([])
- end
-
- it 'returns an empty Array when the user can not read the project' do
- other_project = create(:empty_project, :public)
-
- link['data-project'] = other_project.id.to_s
-
- expect(Ability.abilities).to receive(:allowed?).
- with(user, :read_project, other_project).
- and_return(false)
+ expect(subject).to receive(:can_read_reference?).with(user, project)
- expect(subject.nodes_visible_to_user(user, [link])).to eq([])
+ subject.nodes_visible_to_user(user, [link])
end
end
@@ -221,7 +192,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
it 'delegates the permissions check to the Ability class' do
user = double(:user)
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_project, project)
subject.can?(user, :read_project, project)
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index 0b76d29fce0..412ffa77c36 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -8,6 +8,14 @@ describe Banzai::ReferenceParser::CommitParser, lib: true do
subject { described_class.new(project, user) }
let(:link) { empty_html_link }
+ describe '#nodes_visible_to_user' do
+ context 'when the link has a data-issue attribute' do
+ before { link['data-commit'] = 123 }
+
+ it_behaves_like "referenced feature visibility", "repository"
+ end
+ end
+
describe '#referenced_by' do
context 'when the link has a data-project attribute' do
before do
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index ba982f38542..96e55b0997a 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -8,6 +8,14 @@ describe Banzai::ReferenceParser::CommitRangeParser, lib: true do
subject { described_class.new(project, user) }
let(:link) { empty_html_link }
+ describe '#nodes_visible_to_user' do
+ context 'when the link has a data-issue attribute' do
+ before { link['data-commit-range'] = '123..456' }
+
+ it_behaves_like "referenced feature visibility", "repository"
+ end
+ end
+
describe '#referenced_by' do
context 'when the link has a data-project attribute' do
before do
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index a6ef8394fe7..50a5d1a19ba 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -8,6 +8,14 @@ describe Banzai::ReferenceParser::ExternalIssueParser, lib: true do
subject { described_class.new(project, user) }
let(:link) { empty_html_link }
+ describe '#nodes_visible_to_user' do
+ context 'when the link has a data-issue attribute' do
+ before { link['data-external-issue'] = 123 }
+
+ it_behaves_like "referenced feature visibility", "issues"
+ end
+ end
+
describe '#referenced_by' do
context 'when the link has a data-project attribute' do
before do
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 85cfe728b6a..6873b7b85f9 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -4,10 +4,10 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do
include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) }
- let(:user) { create(:user) }
- let(:issue) { create(:issue, project: project) }
- subject { described_class.new(project, user) }
- let(:link) { empty_html_link }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+ let(:link) { empty_html_link }
+ subject { described_class.new(project, user) }
describe '#nodes_visible_to_user' do
context 'when the link has a data-issue attribute' do
@@ -15,6 +15,8 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do
link['data-issue'] = issue.id.to_s
end
+ it_behaves_like "referenced feature visibility", "issues"
+
it 'returns the nodes when the user can read the issue' do
expect(Ability).to receive(:issues_readable_by_user).
with([issue], user).
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index 77fda47f0e7..8c540d35ddd 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -9,6 +9,14 @@ describe Banzai::ReferenceParser::LabelParser, lib: true do
subject { described_class.new(project, user) }
let(:link) { empty_html_link }
+ describe '#nodes_visible_to_user' do
+ context 'when the link has a data-issue attribute' do
+ before { link['data-label'] = label.id.to_s }
+
+ it_behaves_like "referenced feature visibility", "issues", "merge_requests"
+ end
+ end
+
describe '#referenced_by' do
describe 'when the link has a data-label attribute' do
context 'using an existing label ID' do
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index cf89ad598ea..cb69ca16800 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -8,6 +8,19 @@ describe Banzai::ReferenceParser::MergeRequestParser, lib: true do
subject { described_class.new(merge_request.target_project, user) }
let(:link) { empty_html_link }
+ describe '#nodes_visible_to_user' do
+ context 'when the link has a data-issue attribute' do
+ let(:project) { merge_request.target_project }
+
+ before do
+ project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
+ link['data-merge-request'] = merge_request.id.to_s
+ end
+
+ it_behaves_like "referenced feature visibility", "merge_requests"
+ end
+ end
+
describe '#referenced_by' do
describe 'when the link has a data-merge-request attribute' do
context 'using an existing merge request ID' do
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 6aa45a22cc4..2d4d589ae34 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -9,6 +9,14 @@ describe Banzai::ReferenceParser::MilestoneParser, lib: true do
subject { described_class.new(project, user) }
let(:link) { empty_html_link }
+ describe '#nodes_visible_to_user' do
+ context 'when the link has a data-issue attribute' do
+ before { link['data-milestone'] = milestone.id.to_s }
+
+ it_behaves_like "referenced feature visibility", "issues", "merge_requests"
+ end
+ end
+
describe '#referenced_by' do
describe 'when the link has a data-milestone attribute' do
context 'using an existing milestone ID' do
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index 59127b7c5d1..d217a775802 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -9,6 +9,14 @@ describe Banzai::ReferenceParser::SnippetParser, lib: true do
subject { described_class.new(project, user) }
let(:link) { empty_html_link }
+ describe '#nodes_visible_to_user' do
+ context 'when the link has a data-issue attribute' do
+ before { link['data-snippet'] = snippet.id.to_s }
+
+ it_behaves_like "referenced feature visibility", "snippets"
+ end
+ end
+
describe '#referenced_by' do
describe 'when the link has a data-snippet attribute' do
context 'using an existing snippet ID' do
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 9a82891297d..fafc2cec546 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -82,7 +82,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
end
it 'returns the nodes if the user can read the group' do
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_group, group).
and_return(true)
@@ -90,7 +90,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
end
it 'returns an empty Array if the user can not read the group' do
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_group, group).
and_return(false)
@@ -103,7 +103,9 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
it 'returns the nodes if the attribute value equals the current project ID' do
link['data-project'] = project.id.to_s
- expect(Ability.abilities).not_to receive(:allowed?)
+ # Ensure that we dont call for Ability.allowed?
+ # When project_id in the node is equal to current project ID
+ expect(Ability).not_to receive(:allowed?)
expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
end
@@ -113,7 +115,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
link['data-project'] = other_project.id.to_s
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(true)
@@ -125,7 +127,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
link['data-project'] = other_project.id.to_s
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(false)
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
new file mode 100644
index 00000000000..aaa6b12e67e
--- /dev/null
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe Banzai::Renderer do
+ def expect_render(project = :project)
+ expected_context = { project: project }
+ expect(renderer).to receive(:cacheless_render) { :html }.with(:markdown, expected_context)
+ end
+
+ def expect_cache_update
+ expect(object).to receive(:update_column).with("field_html", :html)
+ end
+
+ def fake_object(*features)
+ markdown = :markdown if features.include?(:markdown)
+ html = :html if features.include?(:html)
+
+ object = double(
+ "object",
+ banzai_render_context: { project: :project },
+ field: markdown,
+ field_html: html
+ )
+
+ allow(object).to receive(:markdown_cache_field_for).with(:field).and_return("field_html")
+ allow(object).to receive(:new_record?).and_return(features.include?(:new))
+ allow(object).to receive(:destroyed?).and_return(features.include?(:destroyed))
+
+ object
+ end
+
+ describe "#render_field" do
+ let(:renderer) { Banzai::Renderer }
+ let(:subject) { renderer.render_field(object, :field) }
+
+ context "with an empty cache" do
+ let(:object) { fake_object(:markdown) }
+ it "caches and returns the result" do
+ expect_render
+ expect_cache_update
+ expect(subject).to eq(:html)
+ end
+ end
+
+ context "with a filled cache" do
+ let(:object) { fake_object(:markdown, :html) }
+
+ it "uses the cache" do
+ expect_render.never
+ expect_cache_update.never
+ should eq(:html)
+ end
+ end
+
+ context "new object" do
+ let(:object) { fake_object(:new, :markdown) }
+
+ it "doesn't cache the result" do
+ expect_render
+ expect_cache_update.never
+ expect(subject).to eq(:html)
+ end
+ end
+
+ context "destroyed object" do
+ let(:object) { fake_object(:destroyed, :markdown) }
+
+ it "doesn't cache the result" do
+ expect_render
+ expect_cache_update.never
+ expect(subject).to eq(:html)
+ end
+ end
+ end
+end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index be51d942af7..84f21631719 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -754,6 +754,20 @@ module Ci
it 'does return production' do
expect(builds.size).to eq(1)
expect(builds.first[:environment]).to eq(environment)
+ expect(builds.first[:options]).to include(environment: { name: environment, action: "start" })
+ end
+ end
+
+ context 'when hash is specified' do
+ let(:environment) do
+ { name: 'production',
+ url: 'http://production.gitlab.com' }
+ end
+
+ it 'does return production and URL' do
+ expect(builds.size).to eq(1)
+ expect(builds.first[:environment]).to eq(environment[:name])
+ expect(builds.first[:options]).to include(environment: environment)
end
end
@@ -770,15 +784,62 @@ module Ci
let(:environment) { 1 }
it 'raises error' do
- expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
+ expect { builds }.to raise_error(
+ 'jobs:deploy_to_production:environment config should be a hash or a string')
end
end
context 'is not a valid string' do
- let(:environment) { 'production staging' }
+ let(:environment) { 'production:staging' }
it 'raises error' do
- expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
+ expect { builds }.to raise_error("jobs:deploy_to_production:environment name #{Gitlab::Regex.environment_name_regex_message}")
+ end
+ end
+
+ context 'when on_stop is specified' do
+ let(:review) { { stage: 'deploy', script: 'test', environment: { name: 'review', on_stop: 'close_review' } } }
+ let(:config) { { review: review, close_review: close_review }.compact }
+
+ context 'with matching job' do
+ let(:close_review) { { stage: 'deploy', script: 'test', environment: { name: 'review', action: 'stop' } } }
+
+ it 'does return a list of builds' do
+ expect(builds.size).to eq(2)
+ expect(builds.first[:environment]).to eq('review')
+ end
+ end
+
+ context 'without matching job' do
+ let(:close_review) { nil }
+
+ it 'raises error' do
+ expect { builds }.to raise_error('review job: on_stop job close_review is not defined')
+ end
+ end
+
+ context 'with close job without environment' do
+ let(:close_review) { { stage: 'deploy', script: 'test' } }
+
+ it 'raises error' do
+ expect { builds }.to raise_error('review job: on_stop job close_review does not have environment defined')
+ end
+ end
+
+ context 'with close job for different environment' do
+ let(:close_review) { { stage: 'deploy', script: 'test', environment: 'production' } }
+
+ it 'raises error' do
+ expect { builds }.to raise_error('review job: on_stop job close_review have different environment name')
+ end
+ end
+
+ context 'with close job without stop action' do
+ let(:close_review) { { stage: 'deploy', script: 'test', environment: { name: 'review' } } }
+
+ it 'raises error' do
+ expect { builds }.to raise_error('review job: on_stop job close_review needs to have action stop defined')
+ end
end
end
end
@@ -1250,5 +1311,40 @@ EOT
end
end
end
+
+ describe "#validation_message" do
+ context "when the YAML could not be parsed" do
+ it "returns an error about invalid configutaion" do
+ content = YAML.dump("invalid: yaml: test")
+
+ expect(GitlabCiYamlProcessor.validation_message(content))
+ .to eq "Invalid configuration format"
+ end
+ end
+
+ context "when the tags parameter is invalid" do
+ it "returns an error about invalid tags" do
+ content = YAML.dump({ rspec: { script: "test", tags: "mysql" } })
+
+ expect(GitlabCiYamlProcessor.validation_message(content))
+ .to eq "jobs:rspec tags should be an array of strings"
+ end
+ end
+
+ context "when YAML content is empty" do
+ it "returns an error about missing content" do
+ expect(GitlabCiYamlProcessor.validation_message(''))
+ .to eq "Please provide content of .gitlab-ci.yml"
+ end
+ end
+
+ context "when the YAML is valid" do
+ it "does not return any errors" do
+ content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+
+ expect(GitlabCiYamlProcessor.validation_message(content)).to be_nil
+ end
+ end
+ end
end
end
diff --git a/spec/lib/ci/mask_secret_spec.rb b/spec/lib/ci/mask_secret_spec.rb
new file mode 100644
index 00000000000..3101bed20fb
--- /dev/null
+++ b/spec/lib/ci/mask_secret_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Ci::MaskSecret, lib: true do
+ subject { described_class }
+
+ describe '#mask' do
+ it 'masks exact number of characters' do
+ expect(mask('token', 'oke')).to eq('txxxn')
+ end
+
+ it 'masks multiple occurrences' do
+ expect(mask('token token token', 'oke')).to eq('txxxn txxxn txxxn')
+ end
+
+ it 'does not mask if not found' do
+ expect(mask('token', 'not')).to eq('token')
+ end
+
+ it 'does support null token' do
+ expect(mask('token', nil)).to eq('token')
+ end
+
+ def mask(value, token)
+ subject.mask!(value.dup, token)
+ end
+ end
+end
diff --git a/spec/lib/constraints/constrainer_helper_spec.rb b/spec/lib/constraints/constrainer_helper_spec.rb
new file mode 100644
index 00000000000..27c8d72aefc
--- /dev/null
+++ b/spec/lib/constraints/constrainer_helper_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe ConstrainerHelper, lib: true do
+ include ConstrainerHelper
+
+ describe '#extract_resource_path' do
+ it { expect(extract_resource_path('/gitlab/')).to eq('gitlab') }
+ it { expect(extract_resource_path('///gitlab//')).to eq('gitlab') }
+ it { expect(extract_resource_path('/gitlab.atom')).to eq('gitlab') }
+
+ context 'relative url' do
+ before do
+ allow(Gitlab::Application.config).to receive(:relative_url_root) { '/gitlab' }
+ end
+
+ it { expect(extract_resource_path('/gitlab/foo')).to eq('foo') }
+ it { expect(extract_resource_path('/foo/bar')).to eq('foo/bar') }
+ end
+ end
+end
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
new file mode 100644
index 00000000000..42299b17c2b
--- /dev/null
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe GroupUrlConstrainer, lib: true do
+ let!(:group) { create(:group, path: 'gitlab') }
+
+ describe '#matches?' do
+ context 'root group' do
+ it { expect(subject.matches?(request '/gitlab')).to be_truthy }
+ it { expect(subject.matches?(request '/gitlab.atom')).to be_truthy }
+ it { expect(subject.matches?(request '/gitlab/edit')).to be_falsey }
+ it { expect(subject.matches?(request '/gitlab-ce')).to be_falsey }
+ it { expect(subject.matches?(request '/.gitlab')).to be_falsey }
+ end
+ end
+
+ def request(path)
+ double(:request, path: path)
+ end
+end
diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb
new file mode 100644
index 00000000000..b3f8530c609
--- /dev/null
+++ b/spec/lib/constraints/user_url_constrainer_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe UserUrlConstrainer, lib: true do
+ let!(:username) { create(:user, username: 'dz') }
+
+ describe '#matches?' do
+ it { expect(subject.matches?(request '/dz')).to be_truthy }
+ it { expect(subject.matches?(request '/dz.atom')).to be_truthy }
+ it { expect(subject.matches?(request '/dz/projects')).to be_falsey }
+ it { expect(subject.matches?(request '/gitlab')).to be_falsey }
+ end
+
+ def request(path)
+ double(:request, path: path)
+ end
+end
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
new file mode 100644
index 00000000000..a6d8e6927e0
--- /dev/null
+++ b/spec/lib/event_filter_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe EventFilter, lib: true do
+ describe '#apply_filter' do
+ let(:source_user) { create(:user) }
+ let!(:public_project) { create(:project, :public) }
+
+ let!(:push_event) { create(:event, action: Event::PUSHED, project: public_project, target: public_project, author: source_user) }
+ let!(:merged_event) { create(:event, action: Event::MERGED, project: public_project, target: public_project, author: source_user) }
+ let!(:comments_event) { create(:event, action: Event::COMMENTED, project: public_project, target: public_project, author: source_user) }
+ let!(:joined_event) { create(:event, action: Event::JOINED, project: public_project, target: public_project, author: source_user) }
+ let!(:left_event) { create(:event, action: Event::LEFT, project: public_project, target: public_project, author: source_user) }
+
+ it 'applies push filter' do
+ events = EventFilter.new(EventFilter.push).apply_filter(Event.all)
+ expect(events).to contain_exactly(push_event)
+ end
+
+ it 'applies merged filter' do
+ events = EventFilter.new(EventFilter.merged).apply_filter(Event.all)
+ expect(events).to contain_exactly(merged_event)
+ end
+
+ it 'applies comments filter' do
+ events = EventFilter.new(EventFilter.comments).apply_filter(Event.all)
+ expect(events).to contain_exactly(comments_event)
+ end
+
+ it 'applies team filter' do
+ events = EventFilter.new(EventFilter.team).apply_filter(Event.all)
+ expect(events).to contain_exactly(joined_event, left_event)
+ end
+
+ it 'applies all filter' do
+ events = EventFilter.new(EventFilter.all).apply_filter(Event.all)
+ expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event)
+ end
+
+ it 'applies no filter' do
+ events = EventFilter.new(nil).apply_filter(Event.all)
+ expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event)
+ end
+
+ it 'applies unknown filter' do
+ events = EventFilter.new('').apply_filter(Event.all)
+ expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event)
+ end
+ end
+end
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
new file mode 100644
index 00000000000..90bc7dad379
--- /dev/null
+++ b/spec/lib/expand_variables_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe ExpandVariables do
+ describe '#expand' do
+ subject { described_class.expand(value, variables) }
+
+ tests = [
+ { value: 'key',
+ result: 'key',
+ variables: []
+ },
+ { value: 'key$variable',
+ result: 'key',
+ variables: []
+ },
+ { value: 'key$variable',
+ result: 'keyvalue',
+ variables: [
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ { value: 'key${variable}',
+ result: 'keyvalue',
+ variables: [
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ { value: 'key$variable$variable2',
+ result: 'keyvalueresult',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ ]
+ },
+ { value: 'key${variable}${variable2}',
+ result: 'keyvalueresult',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ { value: 'key$variable2$variable',
+ result: 'keyresultvalue',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ ]
+ },
+ { value: 'key${variable2}${variable}',
+ result: 'keyresultvalue',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ { value: 'review/$CI_BUILD_REF_NAME',
+ result: 'review/feature/add-review-apps',
+ variables: [
+ { key: 'CI_BUILD_REF_NAME', value: 'feature/add-review-apps' }
+ ]
+ },
+ ]
+
+ tests.each do |test|
+ context "#{test[:value]} resolves to #{test[:result]}" do
+ let(:value) { test[:value] }
+ let(:variables) { test[:variables] }
+
+ it { is_expected.to eq(test[:result]) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 36c77206a3f..0e85e302f29 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -6,6 +6,7 @@ describe ExtractsPath, lib: true do
include Gitlab::Routing.url_helpers
let(:project) { double('project') }
+ let(:request) { double('request') }
before do
@project = project
@@ -15,9 +16,10 @@ describe ExtractsPath, lib: true do
allow(project).to receive(:repository).and_return(repo)
allow(project).to receive(:path_with_namespace).
and_return('gitlab/gitlab-ci')
+ allow(request).to receive(:format=)
end
- describe '#assign_ref' do
+ describe '#assign_ref_vars' do
let(:ref) { sample_commit[:id] }
let(:params) { { path: sample_commit[:line_code_path], ref: ref } }
@@ -30,26 +32,104 @@ describe ExtractsPath, lib: true do
expect(@logs_path).to eq("/#{@project.path_with_namespace}/refs/#{ref}/logs_tree/files/ruby/popen.rb")
end
- context 'escaped slash character in ref' do
- let(:ref) { 'improve%2Fawesome' }
+ context 'ref contains %20' do
+ let(:ref) { 'foo%20bar' }
+
+ it 'is not converted to a space in @id' do
+ @project.repository.add_branch(@project.owner, 'foo%20bar', 'master')
- it 'has no escape sequences in @ref or @logs_path' do
assign_ref_vars
- expect(@ref).to eq('improve/awesome')
- expect(@logs_path).to eq("/#{@project.path_with_namespace}/refs/#{ref}/logs_tree/files/ruby/popen.rb")
+ expect(@id).to start_with('foo%20bar/')
end
end
- context 'ref contains %20' do
- let(:ref) { 'foo%20bar' }
+ context 'path contains space' do
+ let(:params) { { path: 'with space', ref: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } }
- it 'is not converted to a space in @id' do
- @project.repository.add_branch(@project.owner, 'foo%20bar', 'master')
+ it 'is not converted to %20 in @path' do
+ assign_ref_vars
+
+ expect(@path).to eq(params[:path])
+ end
+ end
+
+ context 'subclass overrides get_id' do
+ it 'uses ref returned by get_id' do
+ allow_any_instance_of(self.class).to receive(:get_id){ '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' }
assign_ref_vars
- expect(@id).to start_with('foo%20bar/')
+ expect(@id).to eq(get_id)
+ end
+ end
+
+ context 'ref only exists without .atom suffix' do
+ context 'with a path' do
+ let(:params) { { ref: 'v1.0.0.atom', path: 'README.md' } }
+
+ it 'renders a 404' do
+ expect(self).to receive(:render_404)
+
+ assign_ref_vars
+ end
+ end
+
+ context 'without a path' do
+ let(:params) { { ref: 'v1.0.0.atom' } }
+ before { assign_ref_vars }
+
+ it 'sets the un-suffixed version as @ref' do
+ expect(@ref).to eq('v1.0.0')
+ end
+
+ it 'sets the request format to Atom' do
+ expect(request).to have_received(:format=).with(:atom)
+ end
+ end
+ end
+
+ context 'ref exists with .atom suffix' do
+ context 'with a path' do
+ let(:params) { { ref: 'master.atom', path: 'README.md' } }
+
+ before do
+ repository = @project.repository
+ allow(repository).to receive(:commit).and_call_original
+ allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master'))
+
+ assign_ref_vars
+ end
+
+ it 'sets the suffixed version as @ref' do
+ expect(@ref).to eq('master.atom')
+ end
+
+ it 'does not change the request format' do
+ expect(request).not_to have_received(:format=)
+ end
+ end
+
+ context 'without a path' do
+ let(:params) { { ref: 'master.atom' } }
+
+ before do
+ repository = @project.repository
+ allow(repository).to receive(:commit).and_call_original
+ allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master'))
+ end
+
+ it 'sets the suffixed version as @ref' do
+ assign_ref_vars
+
+ expect(@ref).to eq('master.atom')
+ end
+
+ it 'does not change the request format' do
+ expect(request).not_to receive(:format=)
+
+ assign_ref_vars
+ end
end
end
end
@@ -106,4 +186,18 @@ describe ExtractsPath, lib: true do
end
end
end
+
+ describe '#extract_ref_without_atom' do
+ it 'ignores any matching refs suffixed with atom' do
+ expect(extract_ref_without_atom('master.atom')).to eq('master')
+ end
+
+ it 'returns the longest matching ref' do
+ expect(extract_ref_without_atom('release/app/v1.0.0.atom')).to eq('release/app/v1.0.0')
+ end
+
+ it 'returns nil if there are no matching refs' do
+ expect(extract_ref_without_atom('foo.atom')).to eq(nil)
+ end
+ end
end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index b0772cad312..c9d64e99f88 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -4,14 +4,53 @@ describe Gitlab::Auth, lib: true do
let(:gl_auth) { described_class }
describe 'find_for_git_client' do
- it 'recognizes CI' do
- token = '123'
+ context 'build token' do
+ subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') }
+
+ context 'for running build' do
+ let!(:build) { create(:ci_build, :running) }
+ let(:project) { build.project }
+
+ before do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token')
+ end
+
+ it 'recognises user-less build' do
+ expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities))
+ end
+
+ it 'recognises user token' do
+ build.update(user: create(:user))
+
+ expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities))
+ end
+ end
+
+ (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
+ context "for #{build_status} build" do
+ let!(:build) { create(:ci_build, status: build_status) }
+ let(:project) { build.project }
+
+ before do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token')
+ end
+
+ it 'denies authentication' do
+ expect(subject).to eq(Gitlab::Auth::Result.new)
+ end
+ end
+ end
+ end
+
+ it 'recognizes other ci services' do
project = create(:empty_project)
- project.update_attributes(runners_token: token, builds_enabled: true)
+ project.create_drone_ci_service(active: true)
+ project.drone_ci_service.update(token: 'token')
+
ip = 'ip'
- expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token')
- expect(gl_auth.find_for_git_client('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci))
+ expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'drone-ci-token')
+ expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities))
end
it 'recognizes master passwords' do
@@ -19,7 +58,25 @@ describe Gitlab::Auth, lib: true do
ip = 'ip'
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
- expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap))
+ expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ end
+
+ it 'recognizes user lfs tokens' do
+ user = create(:user)
+ ip = 'ip'
+ token = Gitlab::LfsToken.new(user).token
+
+ expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
+ expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
+ end
+
+ it 'recognizes deploy key lfs tokens' do
+ key = create(:deploy_key)
+ ip = 'ip'
+ token = Gitlab::LfsToken.new(key).token
+
+ expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}")
+ expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
end
it 'recognizes OAuth tokens' do
@@ -29,7 +86,7 @@ describe Gitlab::Auth, lib: true do
ip = 'ip'
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2')
- expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth))
+ expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
end
it 'returns double nil for invalid credentials' do
@@ -91,4 +148,30 @@ describe Gitlab::Auth, lib: true do
end
end
end
+
+ private
+
+ def build_authentication_abilities
+ [
+ :read_project,
+ :build_download_code,
+ :build_read_container_image,
+ :build_create_container_image
+ ]
+ end
+
+ def read_authentication_abilities
+ [
+ :read_project,
+ :download_code,
+ :read_container_image
+ ]
+ end
+
+ def full_authentication_abilities
+ read_authentication_abilities + [
+ :push_code,
+ :create_container_image
+ ]
+ end
end
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index 6e5ba211382..4b08a02ec73 100644
--- a/spec/lib/gitlab/backend/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
+require 'stringio'
describe Gitlab::Shell, lib: true do
let(:project) { double('Project', id: 7, path: 'diaspora') }
@@ -13,7 +14,6 @@ describe Gitlab::Shell, lib: true do
it { is_expected.to respond_to :add_repository }
it { is_expected.to respond_to :remove_repository }
it { is_expected.to respond_to :fork_repository }
- it { is_expected.to respond_to :gc }
it { is_expected.to respond_to :add_namespace }
it { is_expected.to respond_to :rm_namespace }
it { is_expected.to respond_to :mv_namespace }
@@ -21,15 +21,15 @@ describe Gitlab::Shell, lib: true do
it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") }
- describe 'generate_and_link_secret_token' do
+ describe 'memoized secret_token' do
let(:secret_file) { 'tmp/tests/.secret_shell_test' }
let(:link_file) { 'tmp/tests/shell-secret-test/.gitlab_shell_secret' }
before do
- allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test')
allow(Gitlab.config.gitlab_shell).to receive(:secret_file).and_return(secret_file)
+ allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test')
FileUtils.mkdir('tmp/tests/shell-secret-test')
- gitlab_shell.generate_and_link_secret_token
+ Gitlab::Shell.ensure_secret_token!
end
after do
@@ -38,21 +38,47 @@ describe Gitlab::Shell, lib: true do
end
it 'creates and links the secret token file' do
+ secret_token = Gitlab::Shell.secret_token
+
expect(File.exist?(secret_file)).to be(true)
+ expect(File.read(secret_file).chomp).to eq(secret_token)
expect(File.symlink?(link_file)).to be(true)
expect(File.readlink(link_file)).to eq(secret_file)
end
end
+ describe '#add_key' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(Gitlab::Utils).to receive(:system_silent).with(
+ [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
describe Gitlab::Shell::KeyAdder, lib: true do
describe '#add_key' do
- it 'normalizes space characters in the key' do
- io = spy
+ it 'removes trailing garbage' do
+ io = spy(:io)
adder = described_class.new(io)
- adder.add_key('key-42', "sha-rsa foo\tbar\tbaz")
+ adder.add_key('key-42', "ssh-rsa foo bar\tbaz")
+
+ expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
+ end
+
+ it 'raises an exception if the key contains a tab' do
+ expect do
+ described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar")
+ end.to raise_error(Gitlab::Shell::Error)
+ end
- expect(io).to have_received(:puts).with("key-42\tsha-rsa foo bar baz")
+ it 'raises an exception if the key contains a newline' do
+ expect do
+ described_class.new(StringIO.new).add_key('key-42', "ssh-rsa foobar\nssh-rsa pawned")
+ end.to raise_error(Gitlab::Shell::Error)
end
end
end
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb
index 1ff49602486..1547bd3228c 100644
--- a/spec/lib/gitlab/badge/coverage/report_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/report_spec.rb
@@ -44,45 +44,49 @@ describe Gitlab::Badge::Coverage::Report do
end
end
- context 'pipeline exists' do
- let!(:pipeline) do
- create(:ci_pipeline, project: project,
- sha: project.commit.id,
- ref: 'master')
- end
+ context 'when latest successful pipeline exists' do
+ before do
+ create_pipeline do |pipeline|
+ create(:ci_build, :success, pipeline: pipeline, name: 'first', coverage: 40)
+ create(:ci_build, :success, pipeline: pipeline, coverage: 60)
+ end
- context 'builds exist' do
- before do
- create(:ci_build, name: 'first', pipeline: pipeline, coverage: 40)
- create(:ci_build, pipeline: pipeline, coverage: 60)
+ create_pipeline do |pipeline|
+ create(:ci_build, :failed, pipeline: pipeline, coverage: 10)
end
+ end
- context 'particular job specified' do
- let(:job_name) { 'first' }
+ context 'when particular job specified' do
+ let(:job_name) { 'first' }
- it 'returns coverage for the particular job' do
- expect(badge.status).to eq 40
- end
+ it 'returns coverage for the particular job' do
+ expect(badge.status).to eq 40
end
+ end
- context 'particular job not specified' do
- let(:job_name) { '' }
+ context 'when particular job not specified' do
+ let(:job_name) { '' }
+
+ it 'returns arithemetic mean for the pipeline' do
+ expect(badge.status).to eq 50
+ end
+ end
+ end
- it 'returns arithemetic mean for the pipeline' do
- expect(badge.status).to eq 50
- end
+ context 'when only failed pipeline exists' do
+ before do
+ create_pipeline do |pipeline|
+ create(:ci_build, :failed, pipeline: pipeline, coverage: 10)
end
end
- context 'builds do not exist' do
- it_behaves_like 'unknown coverage report'
+ it_behaves_like 'unknown coverage report'
- context 'particular job specified' do
- let(:job_name) { 'nonexistent' }
+ context 'particular job specified' do
+ let(:job_name) { 'nonexistent' }
- it 'retruns nil' do
- expect(badge.status).to be_nil
- end
+ it 'retruns nil' do
+ expect(badge.status).to be_nil
end
end
end
@@ -90,4 +94,13 @@ describe Gitlab::Badge::Coverage::Report do
context 'pipeline does not exist' do
it_behaves_like 'unknown coverage report'
end
+
+ def create_pipeline
+ opts = { project: project, sha: project.commit.id, ref: 'master' }
+
+ create(:ci_pipeline, opts).tap do |pipeline|
+ yield pipeline
+ pipeline.update_status
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/node/cache_spec.rb b/spec/lib/gitlab/ci/config/node/cache_spec.rb
index 50f619ce26e..e251210949c 100644
--- a/spec/lib/gitlab/ci/config/node/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/cache_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Cache do
let(:entry) { described_class.new(config) }
describe 'validations' do
- before { entry.process! }
+ before { entry.compose! }
context 'when entry config value is correct' do
let(:config) do
diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb
new file mode 100644
index 00000000000..df925ff1afd
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb
@@ -0,0 +1,217 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Environment do
+ let(:entry) { described_class.new(config) }
+
+ before { entry.compose! }
+
+ context 'when configuration is a string' do
+ let(:config) { 'production' }
+
+ describe '#string?' do
+ it 'is string configuration' do
+ expect(entry).to be_string
+ end
+ end
+
+ describe '#hash?' do
+ it 'is not hash configuration' do
+ expect(entry).not_to be_hash
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid hash' do
+ expect(entry.value).to include(name: 'production')
+ end
+ end
+
+ describe '#name' do
+ it 'returns environment name' do
+ expect(entry.name).to eq 'production'
+ end
+ end
+
+ describe '#url' do
+ it 'returns environment url' do
+ expect(entry.url).to be_nil
+ end
+ end
+ end
+
+ context 'when configuration is a hash' do
+ let(:config) do
+ { name: 'development', url: 'https://example.gitlab.com' }
+ end
+
+ describe '#string?' do
+ it 'is not string configuration' do
+ expect(entry).not_to be_string
+ end
+ end
+
+ describe '#hash?' do
+ it 'is hash configuration' do
+ expect(entry).to be_hash
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns valid hash' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#name' do
+ it 'returns environment name' do
+ expect(entry.name).to eq 'development'
+ end
+ end
+
+ describe '#url' do
+ it 'returns environment url' do
+ expect(entry.url).to eq 'https://example.gitlab.com'
+ end
+ end
+ end
+
+ context 'when valid action is used' do
+ let(:config) do
+ { name: 'production',
+ action: 'start' }
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ context 'when invalid action is used' do
+ let(:config) do
+ { name: 'production',
+ action: 'invalid' }
+ end
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ describe '#errors' do
+ it 'contains error about invalid action' do
+ expect(entry.errors)
+ .to include 'environment action should be start or stop'
+ end
+ end
+ end
+
+ context 'when on_stop is used' do
+ let(:config) do
+ { name: 'production',
+ on_stop: 'close_app' }
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ context 'when invalid on_stop is used' do
+ let(:config) do
+ { name: 'production',
+ on_stop: false }
+ end
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ describe '#errors' do
+ it 'contains error about invalid action' do
+ expect(entry.errors)
+ .to include 'environment on stop should be a string'
+ end
+ end
+ end
+
+ context 'when variables are used for environment' do
+ let(:config) do
+ { name: 'review/$CI_BUILD_REF_NAME',
+ url: 'https://$CI_BUILD_REF_NAME.review.gitlab.com' }
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when configuration is invalid' do
+ context 'when configuration is an array' do
+ let(:config) { ['env'] }
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ describe '#errors' do
+ it 'contains error about invalid type' do
+ expect(entry.errors)
+ .to include 'environment config should be a hash or a string'
+ end
+ end
+ end
+
+ context 'when environment name is not present' do
+ let(:config) { { url: 'https://example.gitlab.com' } }
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ describe '#errors?' do
+ it 'contains error about missing environment name' do
+ expect(entry.errors)
+ .to include "environment name can't be blank"
+ end
+ end
+ end
+
+ context 'when invalid URL is used' do
+ let(:config) { { name: 'test', url: 'invalid-example.gitlab.com' } }
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+
+ describe '#errors?' do
+ it 'contains error about invalid URL' do
+ expect(entry.errors)
+ .to include "environment url must be a valid url"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb
index d26185ba585..a699089c563 100644
--- a/spec/lib/gitlab/ci/config/node/factory_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb
@@ -65,7 +65,8 @@ describe Gitlab::Ci::Config::Node::Factory do
.value(nil)
.create!
- expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
+ expect(entry)
+ .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified
end
end
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index 2f87d270b36..12232ff7e2f 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Config::Node::Global do
end
context 'when hash is valid' do
- context 'when all entries defined' do
+ context 'when some entries defined' do
let(:hash) do
{ before_script: ['ls', 'pwd'],
image: 'ruby:2.2',
@@ -24,11 +24,11 @@ describe Gitlab::Ci::Config::Node::Global do
stages: ['build', 'pages'],
cache: { key: 'k', untracked: true, paths: ['public/'] },
rspec: { script: %w[rspec ls] },
- spinach: { script: 'spinach' } }
+ spinach: { before_script: [], variables: {}, script: 'spinach' } }
end
- describe '#process!' do
- before { global.process! }
+ describe '#compose!' do
+ before { global.compose! }
it 'creates nodes hash' do
expect(global.descendants).to be_an Array
@@ -59,7 +59,7 @@ describe Gitlab::Ci::Config::Node::Global do
end
end
- context 'when not processed' do
+ context 'when not composed' do
describe '#before_script' do
it 'returns nil' do
expect(global.before_script).to be nil
@@ -73,8 +73,14 @@ describe Gitlab::Ci::Config::Node::Global do
end
end
- context 'when processed' do
- before { global.process! }
+ context 'when composed' do
+ before { global.compose! }
+
+ describe '#errors' do
+ it 'has no errors' do
+ expect(global.errors).to be_empty
+ end
+ end
describe '#before_script' do
it 'returns correct script' do
@@ -137,10 +143,24 @@ describe Gitlab::Ci::Config::Node::Global do
expect(global.jobs).to eq(
rspec: { name: :rspec,
script: %w[rspec ls],
- stage: 'test' },
+ before_script: ['ls', 'pwd'],
+ commands: "ls\npwd\nrspec\nls",
+ image: 'ruby:2.2',
+ services: ['postgres:9.1', 'mysql:5.5'],
+ stage: 'test',
+ cache: { key: 'k', untracked: true, paths: ['public/'] },
+ variables: { VAR: 'value' },
+ after_script: ['make clean'] },
spinach: { name: :spinach,
+ before_script: [],
script: %w[spinach],
- stage: 'test' }
+ commands: 'spinach',
+ image: 'ruby:2.2',
+ services: ['postgres:9.1', 'mysql:5.5'],
+ stage: 'test',
+ cache: { key: 'k', untracked: true, paths: ['public/'] },
+ variables: {},
+ after_script: ['make clean'] },
)
end
end
@@ -148,17 +168,20 @@ describe Gitlab::Ci::Config::Node::Global do
end
context 'when most of entires not defined' do
- let(:hash) { { cache: { key: 'a' }, rspec: { script: %w[ls] } } }
- before { global.process! }
+ before { global.compose! }
+
+ let(:hash) do
+ { cache: { key: 'a' }, rspec: { script: %w[ls] } }
+ end
describe '#nodes' do
it 'instantizes all nodes' do
expect(global.descendants.count).to eq 8
end
- it 'contains undefined nodes' do
+ it 'contains unspecified nodes' do
expect(global.descendants.first)
- .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
+ .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified
end
end
@@ -188,8 +211,11 @@ describe Gitlab::Ci::Config::Node::Global do
# details.
#
context 'when entires specified but not defined' do
- let(:hash) { { variables: nil, rspec: { script: 'rspec' } } }
- before { global.process! }
+ before { global.compose! }
+
+ let(:hash) do
+ { variables: nil, rspec: { script: 'rspec' } }
+ end
describe '#variables' do
it 'undefined entry returns a default value' do
@@ -200,7 +226,7 @@ describe Gitlab::Ci::Config::Node::Global do
end
context 'when hash is not valid' do
- before { global.process! }
+ before { global.compose! }
let(:hash) do
{ before_script: 'ls' }
@@ -247,4 +273,27 @@ describe Gitlab::Ci::Config::Node::Global do
expect(global.specified?).to be true
end
end
+
+ describe '#[]' do
+ before { global.compose! }
+
+ let(:hash) do
+ { cache: { key: 'a' }, rspec: { script: 'ls' } }
+ end
+
+ context 'when node exists' do
+ it 'returns correct entry' do
+ expect(global[:cache])
+ .to be_an_instance_of Gitlab::Ci::Config::Node::Cache
+ expect(global[:jobs][:rspec][:script].value).to eq ['ls']
+ end
+ end
+
+ context 'when node does not exist' do
+ it 'always return unspecified node' do
+ expect(global[:some][:unknown][:node])
+ .not_to be_specified
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb b/spec/lib/gitlab/ci/config/node/hidden_spec.rb
index cc44e2cc054..61e2a554419 100644
--- a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/hidden_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Node::HiddenJob do
+describe Gitlab::Ci::Config::Node::Hidden do
let(:entry) { described_class.new(config) }
describe 'validations' do
context 'when entry config value is correct' do
- let(:config) { { image: 'ruby:2.2' } }
+ let(:config) { [:some, :array] }
describe '#value' do
it 'returns key value' do
- expect(entry.value).to eq(image: 'ruby:2.2')
+ expect(entry.value).to eq [:some, :array]
end
end
@@ -21,17 +21,6 @@ describe Gitlab::Ci::Config::Node::HiddenJob do
end
context 'when entry value is not correct' do
- context 'incorrect config value type' do
- let(:config) { ['incorrect'] }
-
- describe '#errors' do
- it 'saves errors' do
- expect(entry.errors)
- .to include 'hidden job config should be a hash'
- end
- end
- end
-
context 'when config is empty' do
let(:config) { {} }
diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb
index 1484fb60dd8..91f676dae03 100644
--- a/spec/lib/gitlab/ci/config/node/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/job_spec.rb
@@ -3,9 +3,9 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Node::Job do
let(:entry) { described_class.new(config, name: :rspec) }
- before { entry.process! }
-
describe 'validations' do
+ before { entry.compose! }
+
context 'when entry config value is correct' do
let(:config) { { script: 'rspec' } }
@@ -59,28 +59,82 @@ describe Gitlab::Ci::Config::Node::Job do
end
end
- describe '#value' do
- context 'when entry is correct' do
+ describe '#relevant?' do
+ it 'is a relevant entry' do
+ expect(entry).to be_relevant
+ end
+ end
+
+ describe '#compose!' do
+ let(:unspecified) { double('unspecified', 'specified?' => false) }
+
+ let(:specified) do
+ double('specified', 'specified?' => true, value: 'specified')
+ end
+
+ let(:deps) { double('deps', '[]' => unspecified) }
+
+ context 'when job config overrides global config' do
+ before { entry.compose!(deps) }
+
let(:config) do
- { before_script: %w[ls pwd],
- script: 'rspec',
- after_script: %w[cleanup] }
+ { image: 'some_image', cache: { key: 'test' } }
+ end
+
+ it 'overrides global config' do
+ expect(entry[:image].value).to eq 'some_image'
+ expect(entry[:cache].value).to eq(key: 'test')
+ end
+ end
+
+ context 'when job config does not override global config' do
+ before do
+ allow(deps).to receive('[]').with(:image).and_return(specified)
+ entry.compose!(deps)
end
- it 'returns correct value' do
- expect(entry.value)
- .to eq(name: :rspec,
- before_script: %w[ls pwd],
- script: %w[rspec],
- stage: 'test',
- after_script: %w[cleanup])
+ let(:config) { { script: 'ls', cache: { key: 'test' } } }
+
+ it 'uses config from global entry' do
+ expect(entry[:image].value).to eq 'specified'
+ expect(entry[:cache].value).to eq(key: 'test')
end
end
end
- describe '#relevant?' do
- it 'is a relevant entry' do
- expect(entry).to be_relevant
+ context 'when composed' do
+ before { entry.compose! }
+
+ describe '#value' do
+ before { entry.compose! }
+
+ context 'when entry is correct' do
+ let(:config) do
+ { before_script: %w[ls pwd],
+ script: 'rspec',
+ after_script: %w[cleanup] }
+ end
+
+ it 'returns correct value' do
+ expect(entry.value)
+ .to eq(name: :rspec,
+ before_script: %w[ls pwd],
+ script: %w[rspec],
+ commands: "ls\npwd\nrspec",
+ stage: 'test',
+ after_script: %w[cleanup])
+ end
+ end
+ end
+
+ describe '#commands' do
+ let(:config) do
+ { before_script: %w[ls pwd], script: 'rspec' }
+ end
+
+ it 'returns a string of commands concatenated with new line character' do
+ expect(entry.commands).to eq "ls\npwd\nrspec"
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
index b8d9c70479c..929809339ef 100644
--- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
let(:entry) { described_class.new(config) }
describe 'validations' do
- before { entry.process! }
+ before { entry.compose! }
context 'when entry config value is correct' do
let(:config) { { rspec: { script: 'rspec' } } }
@@ -47,8 +47,8 @@ describe Gitlab::Ci::Config::Node::Jobs do
end
end
- context 'when valid job entries processed' do
- before { entry.process! }
+ context 'when valid job entries composed' do
+ before { entry.compose! }
let(:config) do
{ rspec: { script: 'rspec' },
@@ -61,9 +61,11 @@ describe Gitlab::Ci::Config::Node::Jobs do
expect(entry.value).to eq(
rspec: { name: :rspec,
script: %w[rspec],
+ commands: 'rspec',
stage: 'test' },
spinach: { name: :spinach,
script: %w[spinach],
+ commands: 'spinach',
stage: 'test' })
end
end
@@ -74,7 +76,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
expect(entry.descendants.first(2))
.to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job))
expect(entry.descendants.last)
- .to be_an_instance_of(Gitlab::Ci::Config::Node::HiddenJob)
+ .to be_an_instance_of(Gitlab::Ci::Config::Node::Hidden)
end
end
diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb
deleted file mode 100644
index 1ab5478dcfa..00000000000
--- a/spec/lib/gitlab/ci/config/node/null_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Ci::Config::Node::Null do
- let(:null) { described_class.new(nil) }
-
- describe '#leaf?' do
- it 'is leaf node' do
- expect(null).to be_leaf
- end
- end
-
- describe '#valid?' do
- it 'is always valid' do
- expect(null).to be_valid
- end
- end
-
- describe '#errors' do
- it 'is does not contain errors' do
- expect(null.errors).to be_empty
- end
- end
-
- describe '#value' do
- it 'returns nil' do
- expect(null.value).to eq nil
- end
- end
-
- describe '#relevant?' do
- it 'is not relevant' do
- expect(null.relevant?).to eq false
- end
- end
-
- describe '#specified?' do
- it 'is not defined' do
- expect(null.specified?).to eq false
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/config/node/script_spec.rb b/spec/lib/gitlab/ci/config/node/script_spec.rb
index ee7395362a9..219a7e981d3 100644
--- a/spec/lib/gitlab/ci/config/node/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/script_spec.rb
@@ -3,9 +3,7 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Node::Script do
let(:entry) { described_class.new(config) }
- describe '#process!' do
- before { entry.process! }
-
+ describe 'validations' do
context 'when entry config value is correct' do
let(:config) { ['ls', 'pwd'] }
diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
index 2d43e1c1a9d..6bde8602963 100644
--- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
@@ -1,32 +1,41 @@
require 'spec_helper'
describe Gitlab::Ci::Config::Node::Undefined do
- let(:undefined) { described_class.new(entry) }
- let(:entry) { spy('Entry') }
+ let(:entry) { described_class.new }
+
+ describe '#leaf?' do
+ it 'is leaf node' do
+ expect(entry).to be_leaf
+ end
+ end
describe '#valid?' do
- it 'delegates method to entry' do
- expect(undefined.valid).to eq entry
+ it 'is always valid' do
+ expect(entry).to be_valid
end
end
describe '#errors' do
- it 'delegates method to entry' do
- expect(undefined.errors).to eq entry
+ it 'is does not contain errors' do
+ expect(entry.errors).to be_empty
end
end
describe '#value' do
- it 'delegates method to entry' do
- expect(undefined.value).to eq entry
+ it 'returns nil' do
+ expect(entry.value).to eq nil
end
end
- describe '#specified?' do
- it 'is always false' do
- allow(entry).to receive(:specified?).and_return(true)
+ describe '#relevant?' do
+ it 'is not relevant' do
+ expect(entry.relevant?).to eq false
+ end
+ end
- expect(undefined.specified?).to be false
+ describe '#specified?' do
+ it 'is not defined' do
+ expect(entry.specified?).to eq false
end
end
end
diff --git a/spec/lib/gitlab/ci/config/node/unspecified_spec.rb b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb
new file mode 100644
index 00000000000..ba3ceef24ce
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Unspecified do
+ let(:unspecified) { described_class.new(entry) }
+ let(:entry) { spy('Entry') }
+
+ describe '#valid?' do
+ it 'delegates method to entry' do
+ expect(unspecified.valid?).to eq entry
+ end
+ end
+
+ describe '#errors' do
+ it 'delegates method to entry' do
+ expect(unspecified.errors).to eq entry
+ end
+ end
+
+ describe '#value' do
+ it 'delegates method to entry' do
+ expect(unspecified.value).to eq entry
+ end
+ end
+
+ describe '#specified?' do
+ it 'is always false' do
+ allow(entry).to receive(:specified?).and_return(true)
+
+ expect(unspecified.specified?).to be false
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb
new file mode 100644
index 00000000000..b26728a843c
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb
@@ -0,0 +1,115 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::PipelineDuration do
+ let(:calculated_duration) { calculate(data) }
+
+ shared_examples 'calculating duration' do
+ it do
+ expect(calculated_duration).to eq(duration)
+ end
+ end
+
+ context 'test sample A' do
+ let(:data) do
+ [[0, 1],
+ [1, 2],
+ [3, 4],
+ [5, 6]]
+ end
+
+ let(:duration) { 4 }
+
+ it_behaves_like 'calculating duration'
+ end
+
+ context 'test sample B' do
+ let(:data) do
+ [[0, 1],
+ [1, 2],
+ [2, 3],
+ [3, 4],
+ [0, 4]]
+ end
+
+ let(:duration) { 4 }
+
+ it_behaves_like 'calculating duration'
+ end
+
+ context 'test sample C' do
+ let(:data) do
+ [[0, 4],
+ [2, 6],
+ [5, 7],
+ [8, 9]]
+ end
+
+ let(:duration) { 8 }
+
+ it_behaves_like 'calculating duration'
+ end
+
+ context 'test sample D' do
+ let(:data) do
+ [[0, 1],
+ [2, 3],
+ [4, 5],
+ [6, 7]]
+ end
+
+ let(:duration) { 4 }
+
+ it_behaves_like 'calculating duration'
+ end
+
+ context 'test sample E' do
+ let(:data) do
+ [[0, 1],
+ [3, 9],
+ [3, 4],
+ [3, 5],
+ [3, 8],
+ [4, 5],
+ [4, 7],
+ [5, 8]]
+ end
+
+ let(:duration) { 7 }
+
+ it_behaves_like 'calculating duration'
+ end
+
+ context 'test sample F' do
+ let(:data) do
+ [[1, 3],
+ [2, 4],
+ [2, 4],
+ [2, 4],
+ [5, 8]]
+ end
+
+ let(:duration) { 6 }
+
+ it_behaves_like 'calculating duration'
+ end
+
+ context 'test sample G' do
+ let(:data) do
+ [[1, 3],
+ [2, 4],
+ [6, 7]]
+ end
+
+ let(:duration) { 4 }
+
+ it_behaves_like 'calculating duration'
+ end
+
+ def calculate(data)
+ periods = data.shuffle.map do |(first, last)|
+ Gitlab::Ci::PipelineDuration::Period.new(first, last)
+ end
+
+ Gitlab::Ci::PipelineDuration.from_periods(periods.sort_by(&:first))
+ end
+end
diff --git a/spec/lib/gitlab/ci/trace_reader_spec.rb b/spec/lib/gitlab/ci/trace_reader_spec.rb
new file mode 100644
index 00000000000..f06d78694d6
--- /dev/null
+++ b/spec/lib/gitlab/ci/trace_reader_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::TraceReader do
+ let(:path) { __FILE__ }
+ let(:lines) { File.readlines(path) }
+ let(:bytesize) { lines.sum(&:bytesize) }
+
+ it 'returns last few lines' do
+ 10.times do
+ subject = build_subject
+ last_lines = random_lines
+
+ expected = lines.last(last_lines).join
+
+ expect(subject.read(last_lines: last_lines)).to eq(expected)
+ end
+ end
+
+ it 'returns everything if trying to get too many lines' do
+ expect(build_subject.read(last_lines: lines.size * 2)).to eq(lines.join)
+ end
+
+ it 'raises an error if not passing an integer for last_lines' do
+ expect do
+ build_subject.read(last_lines: lines)
+ end.to raise_error(ArgumentError)
+ end
+
+ def random_lines
+ Random.rand(lines.size) + 1
+ end
+
+ def random_buffer
+ Random.rand(bytesize) + 1
+ end
+
+ def build_subject
+ described_class.new(__FILE__, buffer_size: random_buffer)
+ end
+end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index de3f64249a2..1bbaca0739a 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -257,8 +257,9 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
context 'with an external issue tracker reference' do
it 'extracts the referenced issue' do
jira_project = create(:jira_project, name: 'JIRA_EXT1')
+ jira_project.team << [jira_project.creator, :master]
jira_issue = ExternalIssue.new("#{jira_project.name}-1", project: jira_project)
- closing_issue_extractor = described_class.new jira_project
+ closing_issue_extractor = described_class.new(jira_project, jira_project.creator)
message = "Resolve #{jira_issue.to_reference}"
expect(closing_issue_extractor.closed_by_message(message)).to eq([jira_issue])
diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb
new file mode 100644
index 00000000000..39d892c18c0
--- /dev/null
+++ b/spec/lib/gitlab/conflict/file_collection_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::Conflict::FileCollection, lib: true do
+ let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start') }
+ let(:file_collection) { Gitlab::Conflict::FileCollection.new(merge_request) }
+
+ describe '#files' do
+ it 'returns an array of Conflict::Files' do
+ expect(file_collection.files).to all(be_an_instance_of(Gitlab::Conflict::File))
+ end
+ end
+
+ describe '#default_commit_message' do
+ it 'matches the format of the git CLI commit message' do
+ expect(file_collection.default_commit_message).to eq(<<EOM.chomp)
+Merge branch 'conflict-start' into 'conflict-resolvable'
+
+# Conflicts:
+# files/ruby/popen.rb
+# files/ruby/regex.rb
+EOM
+ end
+ end
+end
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
new file mode 100644
index 00000000000..648d342ecf8
--- /dev/null
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -0,0 +1,272 @@
+require 'spec_helper'
+
+describe Gitlab::Conflict::File, lib: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:rugged) { repository.rugged }
+ 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) }
+ let(:index) { rugged.merge_commits(our_commit, their_commit) }
+ let(:conflict) { index.conflicts.last }
+ let(:merge_file_result) { index.merge_file('files/ruby/regex.rb') }
+ let(:conflict_file) { Gitlab::Conflict::File.new(merge_file_result, conflict, merge_request: merge_request) }
+
+ describe '#resolve_lines' do
+ let(:section_keys) { conflict_file.sections.map { |section| section[:id] }.compact }
+
+ context 'when resolving everything to the same side' do
+ let(:resolution_hash) { section_keys.map { |key| [key, 'head'] }.to_h }
+ let(:resolved_lines) { conflict_file.resolve_lines(resolution_hash) }
+ let(:expected_lines) { conflict_file.lines.reject { |line| line.type == 'old' } }
+
+ it 'has the correct number of lines' do
+ expect(resolved_lines.length).to eq(expected_lines.length)
+ end
+
+ it 'has content matching the chosen lines' do
+ expect(resolved_lines.map(&:text)).to eq(expected_lines.map(&:text))
+ end
+ end
+
+ context 'with mixed resolutions' do
+ let(:resolution_hash) do
+ section_keys.map.with_index { |key, i| [key, i.even? ? 'head' : 'origin'] }.to_h
+ end
+
+ let(:resolved_lines) { conflict_file.resolve_lines(resolution_hash) }
+
+ it 'has the correct number of lines' do
+ file_lines = conflict_file.lines.reject { |line| line.type == 'new' }
+
+ expect(resolved_lines.length).to eq(file_lines.length)
+ end
+
+ it 'returns a file containing only the chosen parts of the resolved sections' do
+ expect(resolved_lines.chunk { |line| line.type || 'both' }.map(&:first)).
+ to eq(['both', 'new', 'both', 'old', 'both', 'new', 'both'])
+ end
+ end
+
+ it 'raises MissingResolution when passed a hash without resolutions for all sections' do
+ empty_hash = section_keys.map { |key| [key, nil] }.to_h
+ invalid_hash = section_keys.map { |key| [key, 'invalid'] }.to_h
+
+ expect { conflict_file.resolve_lines({}) }.
+ to raise_error(Gitlab::Conflict::File::MissingResolution)
+
+ expect { conflict_file.resolve_lines(empty_hash) }.
+ to raise_error(Gitlab::Conflict::File::MissingResolution)
+
+ expect { conflict_file.resolve_lines(invalid_hash) }.
+ to raise_error(Gitlab::Conflict::File::MissingResolution)
+ end
+ end
+
+ describe '#highlight_lines!' do
+ def html_to_text(html)
+ CGI.unescapeHTML(ActionView::Base.full_sanitizer.sanitize(html)).delete("\n")
+ end
+
+ it 'modifies the existing lines' do
+ expect { conflict_file.highlight_lines! }.to change { conflict_file.lines.map(&:instance_variables) }
+ end
+
+ it 'is called implicitly when rich_text is accessed on a line' do
+ expect(conflict_file).to receive(:highlight_lines!).once.and_call_original
+
+ conflict_file.lines.each(&:rich_text)
+ end
+
+ it 'sets the rich_text of the lines matching the text content' do
+ conflict_file.lines.each do |line|
+ expect(line.text).to eq(html_to_text(line.rich_text))
+ end
+ end
+ end
+
+ describe '#sections' do
+ it 'only inserts match lines when there is a gap between sections' do
+ conflict_file.sections.each_with_index do |section, i|
+ previous_line_number = 0
+ current_line_number = section[:lines].map(&:old_line).compact.min
+
+ if i > 0
+ previous_line_number = conflict_file.sections[i - 1][:lines].map(&:old_line).compact.last
+ end
+
+ if current_line_number == previous_line_number + 1
+ expect(section[:lines].first.type).not_to eq('match')
+ else
+ expect(section[:lines].first.type).to eq('match')
+ expect(section[:lines].first.text).to match(/\A@@ -#{current_line_number},\d+ \+\d+,\d+ @@ module Gitlab\Z/)
+ end
+ end
+ end
+
+ it 'sets conflict to false for sections with only unchanged lines' do
+ conflict_file.sections.reject { |section| section[:conflict] }.each do |section|
+ without_match = section[:lines].reject { |line| line.type == 'match' }
+
+ expect(without_match).to all(have_attributes(type: nil))
+ end
+ end
+
+ it 'only includes a maximum of CONTEXT_LINES (plus an optional match line) in context sections' do
+ conflict_file.sections.reject { |section| section[:conflict] }.each do |section|
+ without_match = section[:lines].reject { |line| line.type == 'match' }
+
+ expect(without_match.length).to be <= Gitlab::Conflict::File::CONTEXT_LINES * 2
+ end
+ end
+
+ it 'sets conflict to true for sections with only changed lines' do
+ conflict_file.sections.select { |section| section[:conflict] }.each do |section|
+ section[:lines].each do |line|
+ expect(line.type).to be_in(['new', 'old'])
+ end
+ end
+ end
+
+ it 'adds unique IDs to conflict sections, and not to other sections' do
+ section_ids = []
+
+ conflict_file.sections.each do |section|
+ if section[:conflict]
+ expect(section).to have_key(:id)
+ section_ids << section[:id]
+ else
+ expect(section).not_to have_key(:id)
+ end
+ end
+
+ expect(section_ids.uniq).to eq(section_ids)
+ end
+
+ context 'with an example file' do
+ let(:file) do
+ <<FILE
+ # Ensure there is no match line header here
+ def username_regexp
+ default_regexp
+ end
+
+<<<<<<< files/ruby/regex.rb
+def project_name_regexp
+ /\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/
+end
+
+def name_regexp
+ /\A[a-zA-Z0-9_\-\. ]*\z/
+=======
+def project_name_regex
+ %r{\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z}
+end
+
+def name_regex
+ %r{\A[a-zA-Z0-9_\-\. ]*\z}
+>>>>>>> files/ruby/regex.rb
+end
+
+# Some extra lines
+# To force a match line
+# To be created
+
+def path_regexp
+ default_regexp
+end
+
+<<<<<<< files/ruby/regex.rb
+def archive_formats_regexp
+ /(zip|tar|7z|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/
+=======
+def archive_formats_regex
+ %r{(zip|tar|7z|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)}
+>>>>>>> files/ruby/regex.rb
+end
+
+def git_reference_regexp
+ # Valid git ref regexp, see:
+ # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
+ %r{
+ (?!
+ (?# doesn't begins with)
+ \/| (?# rule #6)
+ (?# doesn't contain)
+ .*(?:
+ [\/.]\.| (?# rule #1,3)
+ \/\/| (?# rule #6)
+ @\{| (?# rule #8)
+ \\ (?# rule #9)
+ )
+ )
+ [^\000-\040\177~^:?*\[]+ (?# rule #4-5)
+ (?# doesn't end with)
+ (?<!\.lock) (?# rule #1)
+ (?<![\/.]) (?# rule #6-7)
+ }x
+end
+
+protected
+
+<<<<<<< files/ruby/regex.rb
+def default_regexp
+ /\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z/
+=======
+def default_regex
+ %r{\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z}
+>>>>>>> files/ruby/regex.rb
+end
+FILE
+ end
+
+ let(:conflict_file) { Gitlab::Conflict::File.new({ data: file }, conflict, merge_request: merge_request) }
+ let(:sections) { conflict_file.sections }
+
+ it 'sets the correct match line headers' do
+ expect(sections[0][:lines].first).to have_attributes(type: 'match', text: '@@ -3,14 +3,14 @@')
+ expect(sections[3][:lines].first).to have_attributes(type: 'match', text: '@@ -19,26 +19,26 @@ def path_regexp')
+ expect(sections[6][:lines].first).to have_attributes(type: 'match', text: '@@ -47,52 +47,52 @@ end')
+ end
+
+ it 'does not add match lines where they are not needed' do
+ expect(sections[1][:lines].first.type).not_to eq('match')
+ expect(sections[2][:lines].first.type).not_to eq('match')
+ expect(sections[4][:lines].first.type).not_to eq('match')
+ expect(sections[5][:lines].first.type).not_to eq('match')
+ expect(sections[7][:lines].first.type).not_to eq('match')
+ end
+
+ it 'creates context sections of the correct length' do
+ expect(sections[0][:lines].reject(&:type).length).to eq(3)
+ expect(sections[2][:lines].reject(&:type).length).to eq(3)
+ expect(sections[3][:lines].reject(&:type).length).to eq(3)
+ expect(sections[5][:lines].reject(&:type).length).to eq(3)
+ expect(sections[6][:lines].reject(&:type).length).to eq(3)
+ expect(sections[8][:lines].reject(&:type).length).to eq(1)
+ end
+ end
+ end
+
+ describe '#as_json' do
+ it 'includes the blob path for the file' do
+ expect(conflict_file.as_json[:blob_path]).
+ to eq("/#{project.namespace.to_param}/#{merge_request.project.to_param}/blob/#{our_commit.oid}/files/ruby/regex.rb")
+ end
+
+ it 'includes the blob icon for the file' do
+ expect(conflict_file.as_json[:blob_icon]).to eq('file-text-o')
+ end
+
+ context 'with the full_content option passed' do
+ it 'includes the full content of the conflict' do
+ expect(conflict_file.as_json(full_content: true)).to have_key(:content)
+ end
+
+ it 'includes the detected language of the conflict file' do
+ expect(conflict_file.as_json(full_content: true)[:blob_ace_mode]).
+ to eq('ruby')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb
new file mode 100644
index 00000000000..16eb3766356
--- /dev/null
+++ b/spec/lib/gitlab/conflict/parser_spec.rb
@@ -0,0 +1,193 @@
+require 'spec_helper'
+
+describe Gitlab::Conflict::Parser, lib: true do
+ let(:parser) { Gitlab::Conflict::Parser.new }
+
+ describe '#parse' do
+ def parse_text(text)
+ parser.parse(text, our_path: 'README.md', their_path: 'README.md')
+ end
+
+ context 'when the file has valid conflicts' do
+ let(:text) do
+ <<CONFLICT
+module Gitlab
+ module Regexp
+ extend self
+
+ def username_regexp
+ default_regexp
+ end
+
+<<<<<<< files/ruby/regex.rb
+ def project_name_regexp
+ /\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/
+ end
+
+ def name_regexp
+ /\A[a-zA-Z0-9_\-\. ]*\z/
+=======
+ def project_name_regex
+ %r{\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z}
+ end
+
+ def name_regex
+ %r{\A[a-zA-Z0-9_\-\. ]*\z}
+>>>>>>> files/ruby/regex.rb
+ end
+
+ def path_regexp
+ default_regexp
+ end
+
+<<<<<<< files/ruby/regex.rb
+ def archive_formats_regexp
+ /(zip|tar|7z|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/
+=======
+ def archive_formats_regex
+ %r{(zip|tar|7z|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)}
+>>>>>>> files/ruby/regex.rb
+ end
+
+ def git_reference_regexp
+ # Valid git ref regexp, see:
+ # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
+ %r{
+ (?!
+ (?# doesn't begins with)
+ \/| (?# rule #6)
+ (?# doesn't contain)
+ .*(?:
+ [\/.]\.| (?# rule #1,3)
+ \/\/| (?# rule #6)
+ @\{| (?# rule #8)
+ \\ (?# rule #9)
+ )
+ )
+ [^\000-\040\177~^:?*\[]+ (?# rule #4-5)
+ (?# doesn't end with)
+ (?<!\.lock) (?# rule #1)
+ (?<![\/.]) (?# rule #6-7)
+ }x
+ end
+
+ protected
+
+<<<<<<< files/ruby/regex.rb
+ def default_regexp
+ /\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z/
+=======
+ def default_regex
+ %r{\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z}
+>>>>>>> files/ruby/regex.rb
+ end
+ end
+end
+CONFLICT
+ end
+
+ let(:lines) do
+ parser.parse(text, our_path: 'files/ruby/regex.rb', their_path: 'files/ruby/regex.rb')
+ end
+
+ it 'sets our lines as new lines' do
+ expect(lines[8..13]).to all(have_attributes(type: 'new'))
+ expect(lines[26..27]).to all(have_attributes(type: 'new'))
+ expect(lines[56..57]).to all(have_attributes(type: 'new'))
+ end
+
+ it 'sets their lines as old lines' do
+ expect(lines[14..19]).to all(have_attributes(type: 'old'))
+ expect(lines[28..29]).to all(have_attributes(type: 'old'))
+ expect(lines[58..59]).to all(have_attributes(type: 'old'))
+ end
+
+ it 'sets non-conflicted lines as both' do
+ expect(lines[0..7]).to all(have_attributes(type: nil))
+ expect(lines[20..25]).to all(have_attributes(type: nil))
+ expect(lines[30..55]).to all(have_attributes(type: nil))
+ expect(lines[60..62]).to all(have_attributes(type: nil))
+ end
+
+ it 'sets consecutive line numbers for index, old_pos, and new_pos' do
+ old_line_numbers = lines.select { |line| line.type != 'new' }.map(&:old_pos)
+ new_line_numbers = lines.select { |line| line.type != 'old' }.map(&:new_pos)
+
+ expect(lines.map(&:index)).to eq(0.upto(62).to_a)
+ expect(old_line_numbers).to eq(1.upto(53).to_a)
+ expect(new_line_numbers).to eq(1.upto(53).to_a)
+ end
+ end
+
+ context 'when the file contents include conflict delimiters' do
+ it 'raises UnexpectedDelimiter when there is a non-start delimiter first' do
+ expect { parse_text('=======') }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text('>>>>>>> README.md') }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text('>>>>>>> some-other-path.md') }.
+ not_to raise_error
+ end
+
+ it 'raises UnexpectedDelimiter when a start delimiter is followed by a non-middle delimiter' do
+ start_text = "<<<<<<< README.md\n"
+ end_text = "\n=======\n>>>>>>> README.md"
+
+ expect { parse_text(start_text + '>>>>>>> README.md' + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text(start_text + start_text + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text(start_text + '>>>>>>> some-other-path.md' + end_text) }.
+ not_to raise_error
+ end
+
+ it 'raises UnexpectedDelimiter when a middle delimiter is followed by a non-end delimiter' do
+ start_text = "<<<<<<< README.md\n=======\n"
+ end_text = "\n>>>>>>> README.md"
+
+ expect { parse_text(start_text + '=======' + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text(start_text + start_text + end_text) }.
+ to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter)
+
+ expect { parse_text(start_text + '>>>>>>> some-other-path.md' + end_text) }.
+ not_to raise_error
+ end
+
+ it 'raises MissingEndDelimiter when there is no end delimiter at the end' do
+ start_text = "<<<<<<< README.md\n=======\n"
+
+ expect { parse_text(start_text) }.
+ to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter)
+
+ expect { parse_text(start_text + '>>>>>>> some-other-path.md') }.
+ to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter)
+ end
+ end
+
+ context 'other file types' do
+ it 'raises UnmergeableFile when lines is blank, indicating a binary file' do
+ expect { parse_text('') }.
+ to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
+
+ expect { parse_text(nil) }.
+ to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
+ end
+
+ it 'raises UnmergeableFile when the file is over 200 KB' do
+ expect { parse_text('a' * 204801) }.
+ to raise_error(Gitlab::Conflict::Parser::UnmergeableFile)
+ end
+
+ it 'raises UnsupportedEncoding when the file contains non-UTF-8 characters' do
+ expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) }.
+ to raise_error(Gitlab::Conflict::Parser::UnsupportedEncoding)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
new file mode 100644
index 00000000000..01b2a55b63c
--- /dev/null
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Gitlab::ContributionsCalendar do
+ let(:contributor) { create(:user) }
+ let(:user) { create(:user) }
+
+ let(:private_project) do
+ create(:empty_project, :private) do |project|
+ create(:project_member, user: contributor, project: project)
+ end
+ end
+
+ let(:public_project) do
+ create(:empty_project, :public) do |project|
+ create(:project_member, user: contributor, project: project)
+ end
+ end
+
+ let(:feature_project) do
+ create(:empty_project, :public, issues_access_level: ProjectFeature::PRIVATE) do |project|
+ create(:project_member, user: contributor, project: project).project
+ end
+ end
+
+ let(:today) { Time.now.to_date }
+ let(:last_week) { today - 7.days }
+ let(:last_year) { today - 1.year }
+
+ before do
+ travel_to today
+ end
+
+ after do
+ travel_back
+ end
+
+ def calendar(current_user = nil)
+ described_class.new(contributor, current_user)
+ end
+
+ def create_event(project, day)
+ @targets ||= {}
+ @targets[project] ||= create(:issue, project: project, author: contributor)
+
+ Event.create!(
+ project: project,
+ action: Event::CREATED,
+ target: @targets[project],
+ author: contributor,
+ created_at: day,
+ )
+ end
+
+ describe '#activity_dates' do
+ it "returns a hash of date => count" do
+ create_event(public_project, last_week)
+ create_event(public_project, last_week)
+ create_event(public_project, today)
+
+ 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)
+
+ 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)
+ end
+ end
+
+ describe '#events_by_date' do
+ it "returns all events for a given date" do
+ e1 = create_event(public_project, today)
+ e2 = create_event(public_project, today)
+ create_event(public_project, last_week)
+
+ expect(calendar.events_by_date(today)).to contain_exactly(e1, e2)
+ end
+
+ it "only shows private events to authorized users" do
+ e1 = create_event(public_project, today)
+ e2 = create_event(private_project, today)
+ e3 = create_event(feature_project, today)
+ create_event(public_project, last_week)
+
+ expect(calendar.events_by_date(today)).to contain_exactly(e1)
+ expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3)
+ end
+ end
+
+ describe '#starting_year' do
+ it "should be the start of last year" do
+ expect(calendar.starting_year).to eq(last_year.year)
+ end
+ end
+
+ describe '#starting_month' do
+ it "should be the start of this month" do
+ expect(calendar.starting_month).to eq(today.month)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index b73434e8dd7..a379f798a16 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -8,13 +8,13 @@ describe Gitlab::DataBuilder::Push, lib: true do
let(:data) { described_class.build_sample(project, user) }
it { expect(data).to be_a(Hash) }
- it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
- it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
+ it { expect(data[:before]).to eq('1b12f15a11fc6e62177bef08f47bc7b5ce50b141') }
+ it { expect(data[:after]).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0') }
it { expect(data[:ref]).to eq('refs/heads/master') }
it { expect(data[:commits].size).to eq(3) }
it { expect(data[:total_commits_count]).to eq(3) }
- it { expect(data[:commits].first[:added]).to eq(['gitlab-grack']) }
- it { expect(data[:commits].first[:modified]).to eq(['.gitmodules']) }
+ it { expect(data[:commits].first[:added]).to eq(['bar/branch-test.txt']) }
+ it { expect(data[:commits].first[:modified]).to eq([]) }
it { expect(data[:commits].first[:removed]).to eq([]) }
include_examples 'project hook data with deprecateds'
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 4ec3f19e03f..7fd25b9e5bf 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -91,63 +91,80 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
describe '#add_column_with_default' do
context 'outside of a transaction' do
- before do
- expect(model).to receive(:transaction_open?).and_return(false)
+ context 'when a column limit is not set' do
+ before do
+ expect(model).to receive(:transaction_open?).and_return(false)
- expect(model).to receive(:transaction).and_yield
+ expect(model).to receive(:transaction).and_yield
- expect(model).to receive(:add_column).
- with(:projects, :foo, :integer, default: nil)
+ expect(model).to receive(:add_column).
+ with(:projects, :foo, :integer, default: nil)
- expect(model).to receive(:change_column_default).
- with(:projects, :foo, 10)
- end
+ expect(model).to receive(:change_column_default).
+ with(:projects, :foo, 10)
+ end
- it 'adds the column while allowing NULL values' do
- expect(model).to receive(:update_column_in_batches).
- with(:projects, :foo, 10)
+ it 'adds the column while allowing NULL values' do
+ expect(model).to receive(:update_column_in_batches).
+ with(:projects, :foo, 10)
- expect(model).not_to receive(:change_column_null)
+ expect(model).not_to receive(:change_column_null)
- model.add_column_with_default(:projects, :foo, :integer,
- default: 10,
- allow_null: true)
- end
+ model.add_column_with_default(:projects, :foo, :integer,
+ default: 10,
+ allow_null: true)
+ end
- it 'adds the column while not allowing NULL values' do
- expect(model).to receive(:update_column_in_batches).
- with(:projects, :foo, 10)
+ it 'adds the column while not allowing NULL values' do
+ expect(model).to receive(:update_column_in_batches).
+ with(:projects, :foo, 10)
- expect(model).to receive(:change_column_null).
- with(:projects, :foo, false)
+ expect(model).to receive(:change_column_null).
+ with(:projects, :foo, false)
- model.add_column_with_default(:projects, :foo, :integer, default: 10)
- end
+ model.add_column_with_default(:projects, :foo, :integer, default: 10)
+ end
- it 'removes the added column whenever updating the rows fails' do
- expect(model).to receive(:update_column_in_batches).
- with(:projects, :foo, 10).
- and_raise(RuntimeError)
+ it 'removes the added column whenever updating the rows fails' do
+ expect(model).to receive(:update_column_in_batches).
+ with(:projects, :foo, 10).
+ and_raise(RuntimeError)
- expect(model).to receive(:remove_column).
- with(:projects, :foo)
+ expect(model).to receive(:remove_column).
+ with(:projects, :foo)
- expect do
- model.add_column_with_default(:projects, :foo, :integer, default: 10)
- end.to raise_error(RuntimeError)
+ expect do
+ model.add_column_with_default(:projects, :foo, :integer, default: 10)
+ end.to raise_error(RuntimeError)
+ end
+
+ it 'removes the added column whenever changing a column NULL constraint fails' do
+ expect(model).to receive(:change_column_null).
+ with(:projects, :foo, false).
+ and_raise(RuntimeError)
+
+ expect(model).to receive(:remove_column).
+ with(:projects, :foo)
+
+ expect do
+ model.add_column_with_default(:projects, :foo, :integer, default: 10)
+ end.to raise_error(RuntimeError)
+ end
end
- it 'removes the added column whenever changing a column NULL constraint fails' do
- expect(model).to receive(:change_column_null).
- with(:projects, :foo, false).
- and_raise(RuntimeError)
+ context 'when a column limit is set' do
+ it 'adds the column with a limit' do
+ allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:transaction).and_yield
+ allow(model).to receive(:update_column_in_batches).with(:projects, :foo, 10)
+ allow(model).to receive(:change_column_null).with(:projects, :foo, false)
+ allow(model).to receive(:change_column_default).with(:projects, :foo, 10)
- expect(model).to receive(:remove_column).
- with(:projects, :foo)
+ expect(model).to receive(:add_column).
+ with(:projects, :foo, :integer, default: nil, limit: 8)
- expect do
- model.add_column_with_default(:projects, :foo, :integer, default: 10)
- end.to raise_error(RuntimeError)
+ model.add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
+ end
end
end
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index 10537bea008..6e8fff6f516 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -339,6 +339,48 @@ describe Gitlab::Diff::Position, lib: true do
end
end
+ describe "position for a file in the initial commit" do
+ let(:commit) { project.commit("1a0b36b3cdad1d2ee32457c102a8c0b7056fa863") }
+
+ subject do
+ described_class.new(
+ old_path: "README.md",
+ new_path: "README.md",
+ old_line: nil,
+ new_line: 1,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.new_file).to be true
+ expect(diff_file.new_path).to eq(subject.new_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.added?).to be true
+ expect(diff_line.new_line).to eq(subject.new_line)
+ expect(diff_line.text).to eq("+testme")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+
describe "#to_json" do
let(:hash) do
{
diff --git a/spec/lib/gitlab/downtime_check/message_spec.rb b/spec/lib/gitlab/downtime_check/message_spec.rb
index 93094cda776..a5a398abf78 100644
--- a/spec/lib/gitlab/downtime_check/message_spec.rb
+++ b/spec/lib/gitlab/downtime_check/message_spec.rb
@@ -5,13 +5,35 @@ describe Gitlab::DowntimeCheck::Message do
it 'returns an ANSI formatted String for an offline migration' do
message = described_class.new('foo.rb', true, 'hello')
- expect(message.to_s).to eq("[\e[32moffline\e[0m]: foo.rb: hello")
+ expect(message.to_s).to eq("[\e[31moffline\e[0m]: foo.rb:\n\nhello\n\n")
end
it 'returns an ANSI formatted String for an online migration' do
message = described_class.new('foo.rb')
- expect(message.to_s).to eq("[\e[31monline\e[0m]: foo.rb")
+ expect(message.to_s).to eq("[\e[32monline\e[0m]: foo.rb")
+ end
+ end
+
+ describe '#reason?' do
+ it 'returns false when no reason is specified' do
+ message = described_class.new('foo.rb')
+
+ expect(message.reason?).to eq(false)
+ end
+
+ it 'returns true when a reason is specified' do
+ message = described_class.new('foo.rb', true, 'hello')
+
+ expect(message.reason?).to eq(true)
+ end
+ end
+
+ describe '#reason' do
+ it 'strips excessive whitespace from the returned String' do
+ message = described_class.new('foo.rb', true, " hello\n world\n\n foo")
+
+ expect(message.reason).to eq("hello\nworld\n\nfoo")
end
end
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 e1153154778..cb3651e3845 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -18,7 +18,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
create(
:user,
email: 'jake@adventuretime.ooo',
- authentication_token: 'auth_token'
+ incoming_email_token: 'auth_token'
)
end
@@ -60,8 +60,8 @@ describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
end
end
- context "when we can't find the authentication_token" do
- let(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") }
+ context "when we can't find the incoming_email_token" do
+ let(:email_raw) { fixture_file("emails/wrong_incoming_email_token.eml") }
it "raises an UserNotFoundError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
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 a2119b0dadf..48660d1dd1b 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -12,10 +12,13 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
let(:email_raw) { fixture_file('emails/valid_reply.eml') }
let(:project) { create(:project, :public) }
- let(:noteable) { create(:issue, project: project) }
let(:user) { create(:user) }
+ let(:note) { create(:diff_note_on_merge_request, project: project) }
+ let(:noteable) { note.noteable }
- let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) }
+ let!(:sent_notification) do
+ SentNotification.record_note(note, user.id, mail_key)
+ end
context "when the recipient address doesn't include a mail key" do
let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") }
@@ -60,6 +63,64 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
it "raises an InvalidNoteError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
end
+
+ context 'because the note was commands only' do
+ let!(:email_raw) { fixture_file("emails/commands_only_reply.eml") }
+
+ context 'and current user cannot update noteable' do
+ it 'raises a CommandsOnlyNoteError' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
+ end
+ end
+
+ context 'and current user can update noteable' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ it 'does not raise an error' do
+ expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
+
+ # One system note is created for the 'close' event
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+
+ expect(noteable.reload).to be_closed
+ expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy
+ end
+ end
+ end
+ end
+
+ context 'when the note contains slash commands' do
+ let!(:email_raw) { fixture_file("emails/commands_in_reply.eml") }
+
+ context 'and current user cannot update noteable' do
+ it 'post a note and does not update the noteable' do
+ expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
+
+ # One system note is created for the new note
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+
+ expect(noteable.reload).to be_open
+ expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
+ end
+ end
+
+ context 'and current user can update noteable' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ it 'post a note and updates the noteable' do
+ expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
+
+ # One system note is created for the new note, one for the 'close' event
+ expect { receiver.execute }.to change { noteable.notes.count }.by(2)
+
+ expect(noteable.reload).to be_closed
+ expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy
+ end
+ end
end
context "when the reply is blank" do
@@ -77,10 +138,11 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
it "creates a comment" do
expect { receiver.execute }.to change { noteable.notes.count }.by(1)
- note = noteable.notes.last
+ new_note = noteable.notes.last
- expect(note.author).to eq(sent_notification.recipient)
- expect(note.note).to include("I could not disagree more.")
+ expect(new_note.author).to eq(sent_notification.recipient)
+ expect(new_note.position).to eq(note.position)
+ expect(new_note.note).to include("I could not disagree more.")
end
it "adds all attachments" do
@@ -99,10 +161,11 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
shared_examples 'an email that contains a mail key' do |header|
it "fetches the mail key from the #{header} header and creates a comment" do
expect { receiver.execute }.to change { noteable.notes.count }.by(1)
- note = noteable.notes.last
+ new_note = noteable.notes.last
- expect(note.author).to eq(sent_notification.recipient)
- expect(note.note).to include('I could not disagree more.')
+ expect(new_note.author).to eq(sent_notification.recipient)
+ expect(new_note.position).to eq(note.position)
+ expect(new_note.note).to include('I could not disagree more.')
end
end
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index fbdb7ea34ac..a366d68a146 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -1,21 +1,51 @@
require 'spec_helper'
-describe Gitlab::ExclusiveLease do
- it 'cannot obtain twice before the lease has expired' do
- lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
- expect(lease.try_obtain).to eq(true)
- expect(lease.try_obtain).to eq(false)
+describe Gitlab::ExclusiveLease, type: :redis do
+ let(:unique_key) { SecureRandom.hex(10) }
+
+ describe '#try_obtain' do
+ it 'cannot obtain twice before the lease has expired' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ expect(lease.try_obtain).to be_present
+ expect(lease.try_obtain).to eq(false)
+ end
+
+ it 'can obtain after the lease has expired' do
+ timeout = 1
+ lease = described_class.new(unique_key, timeout: timeout)
+ lease.try_obtain # start the lease
+ sleep(2 * timeout) # lease should have expired now
+ expect(lease.try_obtain).to be_present
+ end
end
- it 'can obtain after the lease has expired' do
- timeout = 1
- lease = Gitlab::ExclusiveLease.new(unique_key, timeout: timeout)
- lease.try_obtain # start the lease
- sleep(2 * timeout) # lease should have expired now
- expect(lease.try_obtain).to eq(true)
+ describe '#exists?' do
+ it 'returns true for an existing lease' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ lease.try_obtain
+
+ expect(lease.exists?).to eq(true)
+ end
+
+ it 'returns false for a lease that does not exist' do
+ lease = described_class.new(unique_key, timeout: 3600)
+
+ expect(lease.exists?).to eq(false)
+ end
end
- def unique_key
- SecureRandom.hex(10)
+ describe '.cancel' do
+ it 'can cancel a lease' do
+ uuid = new_lease(unique_key)
+ expect(uuid).to be_present
+ expect(new_lease(unique_key)).to eq(false)
+
+ described_class.cancel(unique_key, uuid)
+ expect(new_lease(unique_key)).to be_present
+ end
+
+ def new_lease(key)
+ described_class.new(key, timeout: 3600).try_obtain
+ end
end
end
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
index 0af249d8690..6b3dfebd85d 100644
--- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -2,11 +2,11 @@ require 'spec_helper'
describe Gitlab::Gfm::ReferenceRewriter do
let(:text) { 'some text' }
- let(:old_project) { create(:project) }
- let(:new_project) { create(:project) }
+ let(:old_project) { create(:project, name: 'old') }
+ let(:new_project) { create(:project, name: 'new') }
let(:user) { create(:user) }
- before { old_project.team << [user, :guest] }
+ before { old_project.team << [user, :reporter] }
describe '#rewrite' do
subject do
@@ -62,7 +62,7 @@ describe Gitlab::Gfm::ReferenceRewriter do
it { is_expected.to eq "#{ref}, `#1`, #{ref}, `#1`" }
end
- context 'description with labels' do
+ context 'description with project labels' do
let!(:label) { create(:label, id: 123, name: 'test', project: old_project) }
let(:project_ref) { old_project.to_reference }
@@ -76,6 +76,26 @@ describe Gitlab::Gfm::ReferenceRewriter do
it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} }
end
end
+
+ context 'description with group labels' do
+ let(:old_group) { create(:group) }
+ let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) }
+ let(:project_ref) { old_project.to_reference }
+
+ before do
+ old_project.update(namespace: old_group)
+ end
+
+ context 'label referenced by id' do
+ let(:text) { '#1 and ~321' }
+ it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~321} }
+ end
+
+ context 'label referenced by text' do
+ let(:text) { '#1 and ~"group label"' }
+ it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~321} }
+ end
+ end
end
context 'reference contains milestone' do
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index d0e73d70e6e..502ee9ce209 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -1,10 +1,17 @@
require 'spec_helper'
describe Gitlab::GitAccess, lib: true do
- let(:access) { Gitlab::GitAccess.new(actor, project, 'web') }
+ let(:access) { Gitlab::GitAccess.new(actor, project, 'web', authentication_abilities: authentication_abilities) }
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:actor) { user }
+ let(:authentication_abilities) do
+ [
+ :read_project,
+ :download_code,
+ :push_code
+ ]
+ end
describe '#check with single protocols allowed' do
def disable_protocol(protocol)
@@ -15,7 +22,7 @@ describe Gitlab::GitAccess, lib: true do
context 'ssh disabled' do
before do
disable_protocol('ssh')
- @acc = Gitlab::GitAccess.new(actor, project, 'ssh')
+ @acc = Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities)
end
it 'blocks ssh git push' do
@@ -30,7 +37,7 @@ describe Gitlab::GitAccess, lib: true do
context 'http disabled' do
before do
disable_protocol('http')
- @acc = Gitlab::GitAccess.new(actor, project, 'http')
+ @acc = Gitlab::GitAccess.new(actor, project, 'http', authentication_abilities: authentication_abilities)
end
it 'blocks http push' do
@@ -59,6 +66,7 @@ describe Gitlab::GitAccess, lib: true do
context 'pull code' do
it { expect(subject.allowed?).to be_falsey }
+ it { expect(subject.message).to match(/You are not allowed to download code/) }
end
end
@@ -70,6 +78,7 @@ describe Gitlab::GitAccess, lib: true do
context 'pull code' do
it { expect(subject.allowed?).to be_falsey }
+ it { expect(subject.message).to match(/Your account has been blocked/) }
end
end
@@ -77,6 +86,29 @@ describe Gitlab::GitAccess, lib: true do
context 'pull code' do
it { expect(subject.allowed?).to be_falsey }
end
+
+ context 'when project is public' do
+ let(:public_project) { create(:project, :public) }
+ let(:guest_access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) }
+ subject { guest_access.check('git-upload-pack', '_any') }
+
+ context 'when repository is enabled' do
+ it 'give access to download code' do
+ public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::ENABLED)
+
+ expect(subject.allowed?).to be_truthy
+ end
+ end
+
+ context 'when repository is disabled' do
+ it 'does not give access to download code' do
+ public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
+
+ expect(subject.allowed?).to be_falsey
+ expect(subject.message).to match(/You are not allowed to download code/)
+ end
+ end
+ end
end
describe 'deploy key permissions' do
@@ -111,6 +143,44 @@ describe Gitlab::GitAccess, lib: true do
end
end
end
+
+ describe 'build authentication_abilities permissions' do
+ let(:authentication_abilities) { build_authentication_abilities }
+
+ describe 'owner' do
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ context 'pull code' do
+ it { expect(subject).to be_allowed }
+ end
+ end
+
+ describe 'reporter user' do
+ before { project.team << [user, :reporter] }
+
+ context 'pull code' do
+ it { expect(subject).to be_allowed }
+ end
+ end
+
+ describe 'admin user' do
+ let(:user) { create(:admin) }
+
+ context 'when member of the project' do
+ before { project.team << [user, :reporter] }
+
+ context 'pull code' do
+ it { expect(subject).to be_allowed }
+ end
+ end
+
+ context 'when is not member of the project' do
+ context 'pull code' do
+ it { expect(subject).not_to be_allowed }
+ end
+ end
+ end
+ end
end
describe 'push_access_check' do
@@ -148,6 +218,7 @@ describe Gitlab::GitAccess, lib: true do
end
end
+ # Run permission checks for a user
def self.run_permission_checks(permissions_matrix)
permissions_matrix.keys.each do |role|
describe "#{role} access" do
@@ -157,13 +228,12 @@ describe Gitlab::GitAccess, lib: true do
else
project.team << [user, role]
end
- end
- permissions_matrix[role].each do |action, allowed|
- context action do
- subject { access.push_access_check(changes[action]) }
-
- it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
+ permissions_matrix[role].each do |action, allowed|
+ context action do
+ subject { access.push_access_check(changes[action]) }
+ it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
+ end
end
end
end
@@ -283,41 +353,71 @@ describe Gitlab::GitAccess, lib: true do
end
end
- describe 'deploy key permissions' do
- context 'push code' do
- subject { access.check('git-receive-pack', '_any') }
+ shared_examples 'can not push code' do
+ subject { access.check('git-receive-pack', '_any') }
- context 'when project is authorized' do
- let(:key) { create(:deploy_key, can_push: true) }
- let(:actor) { key }
+ context 'when project is authorized' do
+ before { authorize }
+
+ it { expect(subject).not_to be_allowed }
+ end
- before { key.projects << project }
+ context 'when unauthorized' do
+ context 'to public project' do
+ let(:project) { create(:project, :public) }
it { expect(subject).to be_allowed }
end
- context 'when unauthorized' do
- let(:key) { create(:deploy_key, can_push: false) }
- let(:actor) { key }
+ context 'to internal project' do
+ let(:project) { create(:project, :internal) }
- context 'to public project' do
- let(:project) { create(:project, :public) }
+ it { expect(subject).not_to be_allowed }
+ end
- it { expect(subject).not_to be_allowed }
- end
+ context 'to private project' do
+ let(:project) { create(:project) }
+
+ it { expect(subject).not_to be_allowed }
+ end
+ end
+ end
- context 'to internal project' do
- let(:project) { create(:project, :internal) }
+ describe 'build authentication abilities' do
+ let(:authentication_abilities) { build_authentication_abilities }
- it { expect(subject).not_to be_allowed }
- end
+ it_behaves_like 'can not push code' do
+ def authorize
+ project.team << [user, :reporter]
+ end
+ end
+ end
- context 'to private project' do
- let(:project) { create(:project, :internal) }
+ describe 'deploy key permissions' do
+ let(:key) { create(:deploy_key) }
+ let(:actor) { key }
- it { expect(subject).not_to be_allowed }
- end
+ it_behaves_like 'can not push code' do
+ def authorize
+ key.projects << project
end
end
end
+
+ private
+
+ def build_authentication_abilities
+ [
+ :read_project,
+ :build_download_code
+ ]
+ end
+
+ def full_authentication_abilities
+ [
+ :read_project,
+ :download_code,
+ :push_code
+ ]
+ end
end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 4244b807d41..576aa5c366f 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -1,9 +1,16 @@
require 'spec_helper'
describe Gitlab::GitAccessWiki, lib: true do
- let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web') }
+ let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) }
let(:project) { create(:project) }
let(:user) { create(:user) }
+ let(:authentication_abilities) do
+ [
+ :read_project,
+ :download_code,
+ :push_code
+ ]
+ end
describe 'push_allowed?' do
before do
@@ -11,7 +18,7 @@ describe Gitlab::GitAccessWiki, lib: true do
project.team << [user, :developer]
end
- subject { access.push_access_check(changes) }
+ subject { access.check('git-receive-pack', changes) }
it { expect(subject.allowed?).to be_truthy }
end
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
new file mode 100644
index 00000000000..219198eff60
--- /dev/null
+++ b/spec/lib/gitlab/git_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::Git, lib: true do
+ let(:committer_email) { FFaker::Internet.email }
+
+ # I have to remove periods from the end of the name
+ # This happened when the user's name had a suffix (i.e. "Sr.")
+ # This seems to be what git does under the hood. For example, this commit:
+ #
+ # $ git commit --author='Foo Sr. <foo@example.com>' -m 'Where's my trailing period?'
+ #
+ # results in this:
+ #
+ # $ git show --pretty
+ # ...
+ # Author: Foo Sr <foo@example.com>
+ # ...
+ let(:committer_name) { FFaker::Name.name.chomp("\.") }
+
+ describe 'committer_hash' do
+ it "returns a hash containing the given email and name" do
+ committer_hash = Gitlab::Git::committer_hash(email: committer_email, name: committer_name)
+
+ expect(committer_hash[:email]).to eq(committer_email)
+ expect(committer_hash[:name]).to eq(committer_name)
+ expect(committer_hash[:time]).to be_a(Time)
+ end
+
+ context 'when email is nil' do
+ it "returns nil" do
+ committer_hash = Gitlab::Git::committer_hash(email: nil, name: committer_name)
+
+ expect(committer_hash).to be_nil
+ end
+ end
+
+ context 'when name is nil' do
+ it "returns nil" do
+ committer_hash = Gitlab::Git::committer_hash(email: committer_email, name: nil)
+
+ expect(committer_hash).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index 613c47d55f1..e829b936343 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -66,6 +66,6 @@ describe Gitlab::GithubImport::Client, lib: true do
stub_request(:get, /api.github.com/)
allow(client.api).to receive(:rate_limit!).and_raise(Octokit::NotFound)
- expect { client.issues }.not_to raise_error
+ expect { client.issues {} }.not_to raise_error
end
end
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
index 9ae02a6c45f..c520a9c53ad 100644
--- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -73,6 +73,12 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
end
+
+ it 'returns note without created at tag line' do
+ create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(comment.attributes.fetch(:note)).to eq("I'm having a problem with this.")
+ end
end
end
end
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
new file mode 100644
index 00000000000..7478f86bd28
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -0,0 +1,173 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::Importer, lib: true do
+ describe '#execute' do
+ before do
+ allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
+ end
+
+ context 'when an error occurs' do
+ let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_access_level: ProjectFeature::DISABLED) }
+ let(:octocat) { double(id: 123456, login: 'octocat') }
+ let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+ let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
+ let(:repository) { double(id: 1, fork: false) }
+ let(:source_sha) { create(:commit, project: project).id }
+ let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) }
+ let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
+ let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
+
+ let(:label1) do
+ double(
+ name: 'Bug',
+ color: 'ff0000',
+ url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug'
+ )
+ end
+
+ let(:label2) do
+ double(
+ name: nil,
+ color: 'ff0000',
+ url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug'
+ )
+ end
+
+ let(:milestone) do
+ double(
+ number: 1347,
+ state: 'open',
+ title: '1.0',
+ description: 'Version 1.0',
+ due_on: nil,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ url: 'https://api.github.com/repos/octocat/Hello-World/milestones/1'
+ )
+ end
+
+ let(:issue1) do
+ double(
+ number: 1347,
+ milestone: nil,
+ state: 'open',
+ title: 'Found a bug',
+ body: "I'm having a problem with this.",
+ assignee: nil,
+ user: octocat,
+ comments: 0,
+ pull_request: nil,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347',
+ labels: [double(name: 'Label #1')],
+ )
+ end
+
+ let(:issue2) do
+ double(
+ number: 1348,
+ milestone: nil,
+ state: 'open',
+ title: nil,
+ body: "I'm having a problem with this.",
+ assignee: nil,
+ user: octocat,
+ comments: 0,
+ pull_request: nil,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348',
+ labels: [double(name: 'Label #2')],
+ )
+ end
+
+ let(:pull_request) do
+ double(
+ number: 1347,
+ milestone: nil,
+ state: 'open',
+ title: 'New feature',
+ body: 'Please pull these awesome changes',
+ head: source_branch,
+ base: target_branch,
+ assignee: nil,
+ user: octocat,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ merged_at: nil,
+ url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347',
+ labels: [double(name: 'Label #3')],
+ )
+ end
+
+ let(:release1) do
+ double(
+ tag_name: 'v1.0.0',
+ name: 'First release',
+ body: 'Release v1.0.0',
+ draft: false,
+ created_at: created_at,
+ updated_at: updated_at,
+ url: 'https://api.github.com/repos/octocat/Hello-World/releases/1'
+ )
+ end
+
+ let(:release2) do
+ double(
+ tag_name: 'v2.0.0',
+ name: 'Second release',
+ body: nil,
+ draft: false,
+ created_at: created_at,
+ updated_at: updated_at,
+ url: 'https://api.github.com/repos/octocat/Hello-World/releases/2'
+ )
+ end
+
+ before do
+ allow(project).to receive(:import_data).and_return(double.as_null_object)
+ allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
+ allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
+ allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
+ allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
+ allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
+ allow_any_instance_of(Octokit::Client).to receive(:issues_comments).and_return([])
+ allow_any_instance_of(Octokit::Client).to receive(:pull_requests_comments).and_return([])
+ allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
+ allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
+ allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
+ end
+
+ it 'returns true' do
+ expect(described_class.new(project).execute).to eq true
+ end
+
+ it 'does not raise an error' do
+ expect { described_class.new(project).execute }.not_to raise_error
+ end
+
+ it 'stores error messages' do
+ error = {
+ message: 'The remote data could not be fully imported.',
+ errors: [
+ { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
+ { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" },
+ { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." },
+ { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." },
+ { type: :wiki, errors: "Gitlab::Shell::Error" },
+ { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" }
+ ]
+ }
+
+ described_class.new(project).execute
+
+ expect(project.import_error).to eq error.to_json
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
index 0e7ffbe9b8e..c2f1f6b91a1 100644
--- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -48,8 +48,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
context 'when issue is closed' do
- let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
- let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
+ let(:raw_data) { double(base_data.merge(state: 'closed')) }
it 'returns formatted attributes' do
expected = {
@@ -62,7 +61,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
- updated_at: closed_at
+ updated_at: updated_at
}
expect(issue.attributes).to eq(expected)
@@ -110,6 +109,12 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
expect(issue.attributes.fetch(:author_id)).to eq gl_user.id
end
+
+ it 'returns description without created at tag line' do
+ create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(issue.attributes.fetch(:description)).to eq("I'm having a problem with this.")
+ end
end
end
diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb
index 87593e32db0..8098754d735 100644
--- a/spec/lib/gitlab/github_import/label_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb
@@ -1,18 +1,34 @@
require 'spec_helper'
describe Gitlab::GithubImport::LabelFormatter, lib: true do
- describe '#attributes' do
- it 'returns formatted attributes' do
- project = create(:project)
- raw = double(name: 'improvements', color: 'e6e6e6')
+ let(:project) { create(:project) }
+ let(:raw) { double(name: 'improvements', color: 'e6e6e6') }
- formatter = described_class.new(project, raw)
+ subject { described_class.new(project, raw) }
- expect(formatter.attributes).to eq({
+ describe '#attributes' do
+ it 'returns formatted attributes' do
+ expect(subject.attributes).to eq({
project: project,
title: 'improvements',
color: '#e6e6e6'
})
end
end
+
+ describe '#create!' do
+ context 'when label does not exist' do
+ it 'creates a new label' do
+ expect { subject.create! }.to change(Label, :count).by(1)
+ end
+ end
+
+ context 'when label exists' do
+ it 'does not create a new label' do
+ project.labels.create(name: raw.name)
+
+ expect { subject.create! }.not_to change(Label, :count)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
index 5a421e50581..09337c99a07 100644
--- a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
@@ -40,8 +40,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
end
context 'when milestone is closed' do
- let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
- let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
+ let(:raw_data) { double(base_data.merge(state: 'closed')) }
it 'returns formatted attributes' do
expected = {
@@ -52,7 +51,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
state: 'closed',
due_date: nil,
created_at: created_at,
- updated_at: closed_at
+ updated_at: updated_at
}
expect(formatter.attributes).to eq(expected)
diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb
index 0f363b8b0aa..a73b1f4ff5d 100644
--- a/spec/lib/gitlab/github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/github_import/project_creator_spec.rb
@@ -2,33 +2,79 @@ require 'spec_helper'
describe Gitlab::GithubImport::ProjectCreator, lib: true do
let(:user) { create(:user) }
+ let(:namespace) { create(:group, owner: user) }
+
let(:repo) do
OpenStruct.new(
login: 'vim',
name: 'vim',
- private: true,
full_name: 'asd/vim',
- clone_url: "https://gitlab.com/asd/vim.git",
- owner: OpenStruct.new(login: "john")
+ clone_url: 'https://gitlab.com/asd/vim.git'
)
end
- let(:namespace) { create(:group, owner: user) }
- let(:token) { "asdffg" }
- let(:access_params) { { github_access_token: token } }
+
+ subject(:service) { described_class.new(repo, repo.name, namespace, user, github_access_token: 'asdffg') }
before do
namespace.add_owner(user)
+ allow_any_instance_of(Project).to receive(:add_import_job)
end
- it 'creates project' do
- allow_any_instance_of(Project).to receive(:add_import_job)
+ describe '#execute' do
+ it 'creates a project' do
+ expect { service.execute }.to change(Project, :count).by(1)
+ end
+
+ it 'handle GitHub credentials' do
+ project = service.execute
+
+ expect(project.import_url).to eq('https://asdffg@gitlab.com/asd/vim.git')
+ expect(project.safe_import_url).to eq('https://*****@gitlab.com/asd/vim.git')
+ expect(project.import_data.credentials).to eq(user: 'asdffg', password: nil)
+ end
+
+ context 'when GitHub project is private' do
+ it 'sets project visibility to private' do
+ repo.private = true
+
+ project = service.execute
+
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+ end
+
+ context 'when GitHub project is public' do
+ before do
+ allow_any_instance_of(ApplicationSetting).to receive(:default_project_visibility).and_return(Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'sets project visibility to the default project visibility' do
+ repo.private = false
+
+ project = service.execute
+
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ end
+ end
+
+ context 'when GitHub project has wiki' do
+ it 'does not create the wiki repository' do
+ allow(repo).to receive(:has_wiki?).and_return(true)
+
+ project = service.execute
+
+ expect(project.wiki.repository_exists?).to eq false
+ end
+ end
+
+ context 'when GitHub project does not have wiki' do
+ it 'creates the wiki repository' do
+ allow(repo).to receive(:has_wiki?).and_return(false)
- project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user, access_params)
- project = project_creator.execute
+ project = service.execute
- expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git")
- expect(project.safe_import_url).to eq("https://*****@gitlab.com/asd/vim.git")
- expect(project.import_data.credentials).to eq(user: "asdffg", password: nil)
- expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ expect(project.wiki.repository_exists?).to eq true
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index aa28e360993..302f0fc0623 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -27,7 +27,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
created_at: created_at,
updated_at: updated_at,
closed_at: nil,
- merged_at: nil
+ merged_at: nil,
+ url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347'
}
end
@@ -61,8 +62,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'when pull request is closed' do
- let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
- let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
+ let(:raw_data) { double(base_data.merge(state: 'closed')) }
it 'returns formatted attributes' do
expected = {
@@ -80,7 +80,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
- updated_at: closed_at
+ updated_at: updated_at
}
expect(pull_request.attributes).to eq(expected)
@@ -107,7 +107,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
- updated_at: merged_at
+ updated_at: updated_at
}
expect(pull_request.attributes).to eq(expected)
@@ -140,6 +140,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
end
+
+ it 'returns description without created at tag line' do
+ create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(pull_request.attributes.fetch(:description)).to eq('Please pull these awesome changes')
+ end
end
context 'when it has a milestone' do
@@ -229,4 +235,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
end
end
+
+ describe '#url' do
+ let(:raw_data) { double(base_data) }
+
+ it 'return raw url' do
+ expect(pull_request.url).to eq 'https://api.github.com/repos/octocat/Hello-World/pulls/1347'
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb
new file mode 100644
index 00000000000..793128c6ab9
--- /dev/null
+++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::ReleaseFormatter, lib: true do
+ let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
+ let(:octocat) { double(id: 123456, login: 'octocat') }
+ let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+
+ let(:base_data) do
+ {
+ tag_name: 'v1.0.0',
+ name: 'First release',
+ draft: false,
+ created_at: created_at,
+ published_at: created_at,
+ body: 'Release v1.0.0'
+ }
+ end
+
+ subject(:release) { described_class.new(project, raw_data) }
+
+ describe '#attributes' do
+ let(:raw_data) { double(base_data) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ project: project,
+ tag: 'v1.0.0',
+ description: 'Release v1.0.0',
+ created_at: created_at,
+ updated_at: created_at
+ }
+
+ expect(release.attributes).to eq(expected)
+ end
+ end
+
+ describe '#valid' do
+ context 'when release is not a draft' do
+ let(:raw_data) { double(base_data) }
+
+ it 'returns true' do
+ expect(release.valid?).to eq true
+ end
+ end
+
+ context 'when release is draft' do
+ let(:raw_data) { double(base_data.merge(draft: true)) }
+
+ it 'returns false' do
+ expect(release.valid?).to eq false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb
index d3f1deb3837..9b499b593d3 100644
--- a/spec/lib/gitlab/gitlab_import/importer_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb
@@ -13,6 +13,7 @@ describe Gitlab::GitlabImport::Importer, lib: true do
'title' => 'Issue',
'description' => 'Lorem ipsum',
'state' => 'opened',
+ 'confidential' => true,
'author' => {
'id' => 283999,
'name' => 'John Doe'
@@ -34,6 +35,7 @@ describe Gitlab::GitlabImport::Importer, lib: true do
title: 'Issue',
description: "*Created by: John Doe*\n\nLorem ipsum",
state: 'opened',
+ confidential: true,
author_id: project.creator_id
}
diff --git a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb
deleted file mode 100644
index 946712ca38e..00000000000
--- a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::GitoriousImport::ProjectCreator, lib: true do
- let(:user) { create(:user) }
- let(:repo) { Gitlab::GitoriousImport::Repository.new('foo/bar-baz-qux') }
- let(:namespace){ create(:group, owner: user) }
-
- before do
- namespace.add_owner(user)
- end
-
- it 'creates project' do
- allow_any_instance_of(Project).to receive(:add_import_job)
-
- project_creator = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, user)
- project = project_creator.execute
-
- expect(project.name).to eq("Bar Baz Qux")
- expect(project.path).to eq("bar-baz-qux")
- expect(project.namespace).to eq(namespace)
- expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
- expect(project.import_type).to eq("gitorious")
- expect(project.import_source).to eq("foo/bar-baz-qux")
- expect(project.import_url).to eq("https://gitorious.org/foo/bar-baz-qux.git")
- end
-end
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index 54f85f8cffc..097861fd34d 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -15,6 +15,7 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do
subject { described_class.new(project) }
before do
+ project.team << [project.creator, :master]
project.create_import_data(data: import_data)
end
@@ -31,9 +32,9 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do
subject.execute
%w(
- Type-Defect Type-Enhancement Type-Task Type-Review Type-Other Milestone-0.12 Priority-Critical
- Priority-High Priority-Medium Priority-Low OpSys-All OpSys-Windows OpSys-Linux OpSys-OSX Security
- Performance Usability Maintainability Component-Panel Component-Taskbar Component-Battery
+ Type-Defect Type-Enhancement Type-Task Type-Review Type-Other Milestone-0.12 Priority-Critical
+ Priority-High Priority-Medium Priority-Low OpSys-All OpSys-Windows OpSys-Linux OpSys-OSX Security
+ Performance Usability Maintainability Component-Panel Component-Taskbar Component-Battery
Component-Systray Component-Clock Component-Launcher Component-Tint2conf Component-Docs Component-New
).each do |label|
label.sub!("-", ": ")
diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb
new file mode 100644
index 00000000000..47d6f1007d1
--- /dev/null
+++ b/spec/lib/gitlab/identifier_spec.rb
@@ -0,0 +1,123 @@
+require 'spec_helper'
+
+describe Gitlab::Identifier do
+ let(:identifier) do
+ Class.new { include Gitlab::Identifier }.new
+ end
+
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:key) { create(:key, user: user) }
+
+ describe '#identify' do
+ context 'without an identifier' do
+ it 'identifies the user using a commit' do
+ expect(identifier).to receive(:identify_using_commit).
+ with(project, '123')
+
+ identifier.identify('', project, '123')
+ end
+ end
+
+ context 'with a user identifier' do
+ it 'identifies the user using a user ID' do
+ expect(identifier).to receive(:identify_using_user).
+ with("user-#{user.id}")
+
+ identifier.identify("user-#{user.id}", project, '123')
+ end
+ end
+
+ context 'with an SSH key identifier' do
+ it 'identifies the user using an SSH key ID' do
+ expect(identifier).to receive(:identify_using_ssh_key).
+ with("key-#{key.id}")
+
+ identifier.identify("key-#{key.id}", project, '123')
+ end
+ end
+ end
+
+ describe '#identify_using_commit' do
+ it "returns the User for an existing commit author's Email address" do
+ commit = double(:commit, author_email: user.email)
+
+ expect(project).to receive(:commit).with('123').and_return(commit)
+
+ expect(identifier.identify_using_commit(project, '123')).to eq(user)
+ end
+
+ it 'returns nil when no user could be found' do
+ allow(project).to receive(:commit).with('123').and_return(nil)
+
+ expect(identifier.identify_using_commit(project, '123')).to be_nil
+ end
+
+ it 'returns nil when the commit does not have an author Email' do
+ commit = double(:commit, author_email: nil)
+
+ expect(project).to receive(:commit).with('123').and_return(commit)
+
+ expect(identifier.identify_using_commit(project, '123')).to be_nil
+ end
+
+ it 'caches the found users per Email' do
+ commit = double(:commit, author_email: user.email)
+
+ expect(project).to receive(:commit).with('123').twice.and_return(commit)
+ expect(User).to receive(:find_by).once.and_call_original
+
+ 2.times do
+ expect(identifier.identify_using_commit(project, '123')).to eq(user)
+ end
+ end
+ end
+
+ describe '#identify_using_user' do
+ it 'returns the User for an existing ID in the identifier' do
+ found = identifier.identify_using_user("user-#{user.id}")
+
+ expect(found).to eq(user)
+ end
+
+ it 'returns nil for a non existing user ID' do
+ found = identifier.identify_using_user('user--1')
+
+ expect(found).to be_nil
+ end
+
+ it 'caches the found users per ID' do
+ expect(User).to receive(:find_by).once.and_call_original
+
+ 2.times do
+ found = identifier.identify_using_user("user-#{user.id}")
+
+ expect(found).to eq(user)
+ end
+ end
+ end
+
+ describe '#identify_using_ssh_key' do
+ it 'returns the User for an existing SSH key' do
+ found = identifier.identify_using_ssh_key("key-#{key.id}")
+
+ expect(found).to eq(user)
+ end
+
+ it 'returns nil for an invalid SSH key' do
+ found = identifier.identify_using_ssh_key('key--1')
+
+ expect(found).to be_nil
+ end
+
+ it 'caches the found users per key' do
+ expect(User).to receive(:find_by_ssh_key_id).once.and_call_original
+
+ 2.times do
+ found = identifier.identify_using_ssh_key("key-#{key.id}")
+
+ expect(found).to eq(user)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
new file mode 100644
index 00000000000..02b11bd999a
--- /dev/null
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -0,0 +1,191 @@
+---
+issues:
+- subscriptions
+- award_emoji
+- author
+- assignee
+- updated_by
+- milestone
+- notes
+- label_links
+- labels
+- todos
+- user_agent_detail
+- moved_to
+- events
+- merge_requests_closing_issues
+- metrics
+events:
+- author
+- project
+- target
+notes:
+- award_emoji
+- project
+- noteable
+- author
+- updated_by
+- resolved_by
+- todos
+- events
+label_links:
+- target
+- label
+label:
+- subscriptions
+- project
+- lists
+- label_links
+- issues
+- merge_requests
+- priorities
+milestone:
+- project
+- issues
+- labels
+- merge_requests
+- participants
+- events
+snippets:
+- author
+- project
+- notes
+- award_emoji
+releases:
+- project
+project_members:
+- created_by
+- user
+- source
+- project
+merge_requests:
+- subscriptions
+- award_emoji
+- author
+- assignee
+- updated_by
+- milestone
+- notes
+- label_links
+- labels
+- todos
+- target_project
+- source_project
+- merge_user
+- merge_request_diffs
+- merge_request_diff
+- events
+- merge_requests_closing_issues
+- metrics
+merge_request_diff:
+- merge_request
+pipelines:
+- project
+- user
+- statuses
+- builds
+- trigger_requests
+statuses:
+- project
+- pipeline
+- user
+variables:
+- project
+triggers:
+- project
+- trigger_requests
+deploy_keys:
+- user
+- deploy_keys_projects
+- projects
+services:
+- project
+- service_hook
+hooks:
+- project
+protected_branches:
+- project
+- merge_access_levels
+- push_access_levels
+merge_access_levels:
+- protected_branch
+push_access_levels:
+- protected_branch
+project:
+- taggings
+- base_tags
+- tag_taggings
+- tags
+- creator
+- group
+- namespace
+- boards
+- last_event
+- services
+- campfire_service
+- drone_ci_service
+- emails_on_push_service
+- builds_email_service
+- pipelines_email_service
+- irker_service
+- pivotaltracker_service
+- hipchat_service
+- flowdock_service
+- assembla_service
+- asana_service
+- gemnasium_service
+- slack_service
+- buildkite_service
+- bamboo_service
+- teamcity_service
+- pushover_service
+- jira_service
+- redmine_service
+- custom_issue_tracker_service
+- bugzilla_service
+- gitlab_issue_tracker_service
+- external_wiki_service
+- forked_project_link
+- forked_from_project
+- forked_project_links
+- forks
+- merge_requests
+- fork_merge_requests
+- issues
+- labels
+- events
+- milestones
+- notes
+- snippets
+- hooks
+- protected_branches
+- project_members
+- users
+- requesters
+- deploy_keys_projects
+- deploy_keys
+- users_star_projects
+- starrers
+- releases
+- lfs_objects_projects
+- lfs_objects
+- project_group_links
+- invited_groups
+- todos
+- notification_settings
+- import_data
+- commit_statuses
+- pipelines
+- builds
+- runner_projects
+- runners
+- variables
+- triggers
+- environments
+- deployments
+- project_feature
+award_emoji:
+- awardable
+- user
+priorities:
+- label \ No newline at end of file
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
new file mode 100644
index 00000000000..63bab0f0d0d
--- /dev/null
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AttributeCleaner, lib: true do
+ let(:relation_class){ double('relation_class').as_null_object }
+ let(:unsafe_hash) do
+ {
+ 'id' => 101,
+ 'service_id' => 99,
+ 'moved_to_id' => 99,
+ 'namespace_id' => 99,
+ 'ci_id' => 99,
+ 'random_project_id' => 99,
+ 'random_id' => 99,
+ 'milestone_id' => 99,
+ 'project_id' => 99,
+ 'user_id' => 99,
+ 'random_id_in_the_middle' => 99,
+ 'notid' => 99
+ }
+ end
+
+ let(:post_safe_hash) do
+ {
+ 'project_id' => 99,
+ 'user_id' => 99,
+ 'random_id_in_the_middle' => 99,
+ 'notid' => 99
+ }
+ end
+
+ it 'removes unwanted attributes from the hash' do
+ # allow(relation_class).to receive(:attribute_method?).and_return(true)
+ parsed_hash = described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class)
+
+ expect(parsed_hash).to eq(post_safe_hash)
+ end
+end
diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
new file mode 100644
index 00000000000..ea65a5dfed1
--- /dev/null
+++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+# Part of the test security suite for the Import/Export feature
+# Checks whether there are new attributes in models that are currently being exported as part of the
+# project Import/Export feature.
+# If there are new attributes, these will have to either be added to this spec in case we want them
+# to be included as part of the export, or blacklist them using the import_export.yml configuration file.
+# Likewise, new models added to import_export.yml, will need to be added with their correspondent attributes
+# to this spec.
+describe 'Import/Export attribute configuration', lib: true do
+ include ConfigurationHelper
+
+ let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
+ let(:relation_names) do
+ names = names_from_tree(config_hash['project_tree'])
+
+ # Remove duplicated or add missing models
+ # - project is not part of the tree, so it has to be added manually.
+ # - milestone, labels have both singular and plural versions in the tree, so remove the duplicates.
+ names.flatten.uniq - ['milestones', 'labels'] + ['project']
+ end
+
+ let(:safe_attributes_file) { 'spec/lib/gitlab/import_export/safe_model_attributes.yml' }
+ let(:safe_model_attributes) { YAML.load_file(safe_attributes_file) }
+
+ it 'has no new columns' do
+ relation_names.each do |relation_name|
+ relation_class = relation_class_for_name(relation_name)
+ relation_attributes = relation_class.new.attributes.keys
+
+ expect(safe_model_attributes[relation_class.to_s]).not_to be_nil, "Expected exported class #{relation_class} to exist in safe_model_attributes"
+
+ current_attributes = parsed_attributes(relation_name, relation_attributes)
+ safe_attributes = safe_model_attributes[relation_class.to_s]
+ new_attributes = current_attributes - safe_attributes
+
+ expect(new_attributes).to be_empty, failure_message(relation_class.to_s, new_attributes)
+ end
+ end
+
+ def failure_message(relation_class, new_attributes)
+ <<-MSG
+ It looks like #{relation_class}, which is exported using the project Import/Export, has new attributes: #{new_attributes.join(',')}
+
+ Please add the attribute(s) to SAFE_MODEL_ATTRIBUTES if you consider this can be exported.
+ Otherwise, please blacklist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its correspondent
+ model in the +excluded_attributes+ section.
+
+ SAFE_MODEL_ATTRIBUTES: #{File.expand_path(safe_attributes_file)}
+ IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+ MSG
+ end
+
+ class Author < User
+ end
+end
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
new file mode 100644
index 00000000000..a88ddd17aca
--- /dev/null
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::FileImporter, lib: true do
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+ let(:export_path) { "#{Dir::tmpdir}/file_importer_spec" }
+ let(:valid_file) { "#{shared.export_path}/valid.json" }
+ let(:symlink_file) { "#{shared.export_path}/invalid.json" }
+ let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
+
+ before do
+ stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+ allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
+
+ setup_files
+
+ described_class.import(archive_file: '', shared: shared)
+ end
+
+ after do
+ FileUtils.rm_rf(export_path)
+ end
+
+ it 'removes symlinks in root folder' do
+ expect(File.exist?(symlink_file)).to be false
+ end
+
+ 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
+
+ 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)
+ end
+end
diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb
new file mode 100644
index 00000000000..9b492d1b9c7
--- /dev/null
+++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+# Part of the test security suite for the Import/Export feature
+# Finds if a new model has been added that can potentially be part of the Import/Export
+# If it finds a new model, it will show a +failure_message+ with the options available.
+describe 'Import/Export model configuration', lib: true do
+ include ConfigurationHelper
+
+ let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
+ let(:model_names) do
+ names = names_from_tree(config_hash['project_tree'])
+
+ # Remove duplicated or add missing models
+ # - project is not part of the tree, so it has to be added manually.
+ # - milestone, labels have both singular and plural versions in the tree, so remove the duplicates.
+ # - User, Author... Models we do not care about for checking models
+ names.flatten.uniq - ['milestones', 'labels', 'user', 'author'] + ['project']
+ end
+
+ 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 }
+
+ it 'has no new models' do
+ model_names.each do |model_name|
+ new_models = Array(current_models[model_name]) - Array(all_models[model_name])
+ expect(new_models).to be_empty, failure_message(model_name.classify, new_models)
+ end
+ end
+
+ # 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']
+ end
+
+ all_models_hash
+ end
+
+ def failure_message(parent_model_name, new_models)
+ <<-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.
+
+ MODELS_JSON: #{File.expand_path(all_models_yml)}
+ IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+ MSG
+ end
+end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index cbbf98dca94..ed9df468ced 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -1,11 +1,22 @@
{
"description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "snippets_enabled": false,
"visibility_level": 10,
"archived": false,
+ "labels": [
+ {
+ "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",
+ "priorities": [
+ ]
+ }
+ ],
"issues": [
{
"id": 40,
@@ -28,7 +39,7 @@
"test_ee_field": "test",
"milestone": {
"id": 1,
- "title": "v0.0",
+ "title": "test milestone",
"project_id": 8,
"description": "test milestone",
"due_date": null,
@@ -55,7 +66,7 @@
{
"id": 2,
"label_id": 2,
- "target_id": 3,
+ "target_id": 40,
"target_type": "Issue",
"created_at": "2016-07-22T08:57:02.840Z",
"updated_at": "2016-07-22T08:57:02.840Z",
@@ -68,7 +79,37 @@
"updated_at": "2016-07-22T08:55:44.161Z",
"template": false,
"description": "",
- "priority": null
+ "type": "ProjectLabel"
+ }
+ },
+ {
+ "id": 3,
+ "label_id": 3,
+ "target_id": 40,
+ "target_type": "Issue",
+ "created_at": "2016-07-22T08:57:02.841Z",
+ "updated_at": "2016-07-22T08:57:02.841Z",
+ "label": {
+ "id": 3,
+ "title": "test3",
+ "color": "#428bca",
+ "group_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "project_id": null,
+ "type": "GroupLabel",
+ "priorities": [
+ {
+ "id": 1,
+ "project_id": 5,
+ "label_id": 1,
+ "priority": 1,
+ "created_at": "2016-10-18T09:35:43.338Z",
+ "updated_at": "2016-10-18T09:35:43.338Z"
+ }
+ ]
}
}
],
@@ -285,6 +326,31 @@
"deleted_at": null,
"due_date": null,
"moved_to_id": null,
+ "milestone": {
+ "id": 1,
+ "title": "test milestone",
+ "project_id": 8,
+ "description": "test milestone",
+ "due_date": null,
+ "created_at": "2016-06-14T15:02:04.415Z",
+ "updated_at": "2016-06-14T15:02:04.415Z",
+ "state": "active",
+ "iid": 1,
+ "events": [
+ {
+ "id": 487,
+ "target_type": "Milestone",
+ "target_id": 1,
+ "title": null,
+ "data": null,
+ "project_id": 46,
+ "created_at": "2016-06-14T15:02:04.418Z",
+ "updated_at": "2016-06-14T15:02:04.418Z",
+ "action": 1,
+ "author_id": 18
+ }
+ ]
+ },
"notes": [
{
"id": 359,
@@ -498,6 +564,27 @@
"deleted_at": null,
"due_date": null,
"moved_to_id": null,
+ "label_links": [
+ {
+ "id": 99,
+ "label_id": 2,
+ "target_id": 38,
+ "target_type": "Issue",
+ "created_at": "2016-07-22T08:57:02.840Z",
+ "updated_at": "2016-07-22T08:57:02.840Z",
+ "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"
+ }
+ }
+ ],
"notes": [
{
"id": 367,
@@ -2185,11 +2272,33 @@
]
}
],
- "labels": [
-
- ],
"milestones": [
{
+ "id": 1,
+ "title": "test milestone",
+ "project_id": 8,
+ "description": "test milestone",
+ "due_date": null,
+ "created_at": "2016-06-14T15:02:04.415Z",
+ "updated_at": "2016-06-14T15:02:04.415Z",
+ "state": "active",
+ "iid": 1,
+ "events": [
+ {
+ "id": 487,
+ "target_type": "Milestone",
+ "target_id": 1,
+ "title": null,
+ "data": null,
+ "project_id": 46,
+ "created_at": "2016-06-14T15:02:04.418Z",
+ "updated_at": "2016-06-14T15:02:04.418Z",
+ "action": 1,
+ "author_id": 18
+ }
+ ]
+ },
+ {
"id": 20,
"title": "v4.0",
"project_id": 5,
@@ -6482,7 +6591,7 @@
{
"id": 37,
"project_id": 5,
- "ref": "master",
+ "ref": null,
"sha": "048721d90c449b244b7b4c53a9186b04330174ec",
"before_sha": null,
"push_data": null,
@@ -6876,6 +6985,7 @@
"note_events": true,
"build_events": true,
"category": "issue_tracker",
+ "type": "CustomIssueTrackerService",
"default": true,
"wiki_page_events": true
},
@@ -7305,6 +7415,41 @@
],
"protected_branches": [
-
- ]
+ {
+ "id": 1,
+ "project_id": 9,
+ "name": "master",
+ "created_at": "2016-08-30T07:32:52.426Z",
+ "updated_at": "2016-08-30T07:32:52.426Z",
+ "merge_access_levels": [
+ {
+ "id": 1,
+ "protected_branch_id": 1,
+ "access_level": 40,
+ "created_at": "2016-08-30T07:32:52.458Z",
+ "updated_at": "2016-08-30T07:32:52.458Z"
+ }
+ ],
+ "push_access_levels": [
+ {
+ "id": 1,
+ "protected_branch_id": 1,
+ "access_level": 40,
+ "created_at": "2016-08-30T07:32:52.490Z",
+ "updated_at": "2016-08-30T07:32:52.490Z"
+ }
+ ]
+ }
+ ],
+ "project_feature": {
+ "builds_access_level": 0,
+ "created_at": "2014-12-26T09:26:45.000Z",
+ "id": 2,
+ "issues_access_level": 0,
+ "merge_requests_access_level": 20,
+ "project_id": 4,
+ "snippets_access_level": 20,
+ "updated_at": "2016-09-23T11:58:28.000Z",
+ "wiki_access_level": 20
+ }
} \ No newline at end of file
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 4d857945fde..3038ab53ad8 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -1,11 +1,12 @@
require 'spec_helper'
+include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do
let(:user) { create(:user) }
let(:namespace) { create(:namespace, owner: user) }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
- let(:project) { create(:empty_project, name: 'project', path: 'project') }
+ let!(:project) { create(:empty_project, name: 'project', path: 'project', builds_access_level: ProjectFeature::DISABLED, issues_access_level: ProjectFeature::DISABLED) }
let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
let(:restored_project_json) { project_tree_restorer.restore }
@@ -18,12 +19,41 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(restored_project_json).to be true
end
+ it 'restore correct project features' do
+ restored_project_json
+ project = Project.find_by_path('project')
+
+ expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.builds_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.snippets_access_level).to eq(ProjectFeature::ENABLED)
+ expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::ENABLED)
+ expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED)
+ end
+
+ it 'has the same label associated to two issues' do
+ restored_project_json
+
+ expect(ProjectLabel.find_by_title('test2').issues.count).to eq(2)
+ end
+
+ it 'has milestones associated to two separate issues' do
+ restored_project_json
+
+ expect(Milestone.find_by_description('test milestone').issues.count).to eq(2)
+ end
+
it 'creates a valid pipeline note' do
restored_project_json
expect(Ci::Pipeline.first.notes).not_to be_empty
end
+ it 'restores pipelines with missing ref' do
+ restored_project_json
+
+ expect(Ci::Pipeline.where(ref: nil)).not_to be_empty
+ end
+
it 'restores the correct event with symbolised data' do
restored_project_json
@@ -38,6 +68,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
end
+ it 'contains the merge access levels on a protected branch' do
+ restored_project_json
+
+ expect(ProtectedBranch.first.merge_access_levels).not_to be_empty
+ end
+
+ it 'contains the push access levels on a protected branch' do
+ restored_project_json
+
+ expect(ProtectedBranch.first.push_access_levels).not_to be_empty
+ end
+
context 'event at forth level of the tree' do
let(:event) { Event.where(title: 'test levels').first }
@@ -66,10 +108,51 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(Label.first.label_links.first.target).not_to be_nil
end
- it 'has milestones associated to issues' do
+ it 'has project labels' do
+ restored_project_json
+
+ expect(ProjectLabel.count).to eq(2)
+ end
+
+ it 'has no group labels' do
+ restored_project_json
+
+ expect(GroupLabel.count).to eq(0)
+ end
+
+ context 'with group' do
+ let!(:project) do
+ create(:empty_project,
+ name: 'project',
+ path: 'project',
+ builds_access_level: ProjectFeature::DISABLED,
+ issues_access_level: ProjectFeature::DISABLED,
+ group: create(:group))
+ end
+
+ it 'has group labels' do
+ restored_project_json
+
+ expect(GroupLabel.count).to eq(1)
+ end
+
+ it 'has label priorities' do
+ restored_project_json
+
+ expect(GroupLabel.first.priorities).not_to be_empty
+ end
+ end
+
+ it 'has a project feature' do
restored_project_json
- expect(Milestone.find_by_description('test milestone').issues).not_to be_empty
+ expect(project.project_feature).not_to be_nil
+ end
+
+ it 'restores the correct service' do
+ restored_project_json
+
+ expect(CustomIssueTrackerService.first).not_to be_nil
end
context 'Merge requests' do
@@ -93,6 +176,19 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(MergeRequest.find_by_title('MR2').source_project_id).to eq(-1)
end
end
+
+ context 'project.json file access check' do
+ it 'does not read a symlink' do
+ Dir.mktmpdir do |tmpdir|
+ setup_symlink(tmpdir, 'project.json')
+ allow(shared).to receive(:export_path).and_call_original
+
+ restored_project_json
+
+ expect(shared.errors.first).not_to include('test')
+ end
+ end
+ end
end
end
end
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 3a86a4ce07c..c8bba553558 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -111,6 +111,30 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
end
+ it 'has project and group labels' do
+ label_types = saved_project_json['issues'].first['label_links'].map { |link| link['label']['type']}
+
+ expect(label_types).to match_array(['ProjectLabel', 'GroupLabel'])
+ end
+
+ it 'has priorities associated to labels' do
+ priorities = saved_project_json['issues'].first['label_links'].map { |link| link['label']['priorities']}
+
+ expect(priorities.flatten).not_to be_empty
+ end
+
+ it 'saves the correct service type' do
+ expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
+ end
+
+ it 'has project feature' do
+ project_feature = saved_project_json['project_feature']
+ expect(project_feature).not_to be_empty
+ expect(project_feature["issues_access_level"]).to eq(ProjectFeature::DISABLED)
+ expect(project_feature["wiki_access_level"]).to eq(ProjectFeature::ENABLED)
+ expect(project_feature["builds_access_level"]).to eq(ProjectFeature::PRIVATE)
+ end
+
it 'does not complain about non UTF-8 characters in MR diffs' do
ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'")
@@ -123,15 +147,20 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
issue = create(:issue, assignee: user)
snippet = create(:project_snippet)
release = create(:release)
+ group = create(:group)
project = create(:project,
:public,
issues: [issue],
snippets: [snippet],
- releases: [release]
+ releases: [release],
+ group: group
)
- label = create(:label, project: project)
- create(:label_link, label: label, target: issue)
+ project_label = create(:label, project: project)
+ group_label = create(:group_label, group: group)
+ create(:label_link, label: project_label, target: issue)
+ create(:label_link, label: group_label, target: issue)
+ create(:label_priority, label: group_label, priority: 1)
milestone = create(:milestone, project: project)
merge_request = create(:merge_request, source_project: project, milestone: milestone)
commit_status = create(:commit_status, project: project)
@@ -153,6 +182,11 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
commit_id: ci_pipeline.sha)
create(:event, target: milestone, project: project, action: Event::CREATED, author: user)
+ create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker')
+
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+ project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
+ project.project_feature.update_attribute(:builds_access_level, ProjectFeature::PRIVATE)
project
end
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index b6dec41d218..3ceb1e7e803 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -32,6 +32,12 @@ describe Gitlab::ImportExport::Reader, lib: true do
expect(described_class.new(shared: shared).project_tree).to match(include: [:issues])
end
+ it 'generates the correct hash for a single project feature relation' do
+ setup_yaml(project_tree: [:project_feature])
+
+ expect(described_class.new(shared: shared).project_tree).to match(include: [:project_feature])
+ end
+
it 'generates the correct hash for a multiple project relation' do
setup_yaml(project_tree: [:issues, :snippets])
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
new file mode 100644
index 00000000000..3aa492a8ab1
--- /dev/null
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -0,0 +1,125 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::RelationFactory, lib: true do
+ let(:project) { create(:empty_project) }
+ let(:members_mapper) { double('members_mapper').as_null_object }
+ let(:user) { create(:user) }
+ let(:created_object) do
+ described_class.create(relation_sym: relation_sym,
+ relation_hash: relation_hash,
+ members_mapper: members_mapper,
+ user: user,
+ project_id: project.id)
+ end
+
+ context 'hook object' do
+ let(:relation_sym) { :hooks }
+ let(:id) { 999 }
+ let(:service_id) { 99 }
+ let(:original_project_id) { 8 }
+ let(:token) { 'secret' }
+
+ let(:relation_hash) do
+ {
+ 'id' => id,
+ 'url' => 'https://example.json',
+ 'project_id' => original_project_id,
+ 'created_at' => '2016-08-12T09:41:03.462Z',
+ 'updated_at' => '2016-08-12T09:41:03.462Z',
+ 'service_id' => service_id,
+ 'push_events' => true,
+ 'issues_events' => false,
+ 'merge_requests_events' => true,
+ 'tag_push_events' => false,
+ 'note_events' => true,
+ 'enable_ssl_verification' => true,
+ 'build_events' => false,
+ 'wiki_page_events' => true,
+ 'token' => token
+ }
+ end
+
+ it 'does not have the original ID' do
+ expect(created_object.id).not_to eq(id)
+ end
+
+ it 'does not have the original service_id' do
+ expect(created_object.service_id).not_to eq(service_id)
+ end
+
+ it 'does not have the original project_id' do
+ expect(created_object.project_id).not_to eq(original_project_id)
+ end
+
+ it 'has the new project_id' do
+ expect(created_object.project_id).to eq(project.id)
+ end
+
+ it 'has a token' do
+ expect(created_object.token).to eq(token)
+ end
+
+ context 'original service exists' do
+ let(:service_id) { Service.create(project: project).id }
+
+ it 'does not have the original service_id' do
+ expect(created_object.service_id).not_to eq(service_id)
+ end
+ end
+ end
+
+ # Mocks an ActiveRecordish object with the dodgy columns
+ class FooModel
+ include ActiveModel::Model
+
+ def initialize(params)
+ params.each { |key, value| send("#{key}=", value) }
+ end
+
+ def values
+ instance_variables.map { |ivar| instance_variable_get(ivar) }
+ end
+ end
+
+ # `project_id`, `described_class.USER_REFERENCES`, noteable_id, target_id, and some project IDs are already
+ # re-assigned by described_class.
+ context 'Potentially hazardous foreign keys' do
+ let(:relation_sym) { :hazardous_foo_model }
+ let(:relation_hash) do
+ {
+ 'service_id' => 99,
+ 'moved_to_id' => 99,
+ 'namespace_id' => 99,
+ 'ci_id' => 99,
+ 'random_project_id' => 99,
+ 'random_id' => 99,
+ 'milestone_id' => 99,
+ 'project_id' => 99,
+ 'user_id' => 99,
+ }
+ end
+
+ class HazardousFooModel < FooModel
+ attr_accessor :service_id, :moved_to_id, :namespace_id, :ci_id, :random_project_id, :random_id, :milestone_id, :project_id
+ end
+
+ it 'does not preserve any foreign key IDs' do
+ expect(created_object.values).not_to include(99)
+ end
+ end
+
+ context 'Project references' do
+ let(:relation_sym) { :project_foo_model }
+ let(:relation_hash) do
+ Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES.map { |ref| { ref => 99 } }.inject(:merge)
+ end
+
+ class ProjectFooModel < FooModel
+ attr_accessor(*Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES)
+ end
+
+ it 'does not preserve any project foreign key IDs' do
+ expect(created_object.values).not_to include(99)
+ 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
new file mode 100644
index 00000000000..07a2c316899
--- /dev/null
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -0,0 +1,342 @@
+---
+Issue:
+- id
+- title
+- assignee_id
+- author_id
+- project_id
+- created_at
+- updated_at
+- position
+- branch_name
+- description
+- state
+- iid
+- updated_by_id
+- confidential
+- deleted_at
+- due_date
+- moved_to_id
+- lock_version
+- milestone_id
+- weight
+Event:
+- id
+- target_type
+- target_id
+- title
+- data
+- project_id
+- created_at
+- updated_at
+- action
+- author_id
+Note:
+- id
+- note
+- noteable_type
+- author_id
+- created_at
+- updated_at
+- project_id
+- attachment
+- line_code
+- commit_id
+- noteable_id
+- system
+- st_diff
+- updated_by_id
+- type
+- position
+- original_position
+- resolved_at
+- resolved_by_id
+- discussion_id
+- original_discussion_id
+LabelLink:
+- id
+- label_id
+- target_id
+- target_type
+- created_at
+- updated_at
+ProjectLabel:
+- id
+- title
+- color
+- group_id
+- project_id
+- type
+- created_at
+- updated_at
+- template
+- description
+- priority
+Milestone:
+- id
+- title
+- project_id
+- description
+- due_date
+- created_at
+- updated_at
+- state
+- iid
+ProjectSnippet:
+- id
+- title
+- content
+- author_id
+- project_id
+- created_at
+- updated_at
+- file_name
+- type
+- visibility_level
+Release:
+- id
+- tag
+- description
+- project_id
+- created_at
+- updated_at
+ProjectMember:
+- id
+- access_level
+- source_id
+- source_type
+- user_id
+- notification_level
+- type
+- created_at
+- updated_at
+- created_by_id
+- invite_email
+- invite_token
+- invite_accepted_at
+- requested_at
+- expires_at
+User:
+- id
+- username
+- email
+MergeRequest:
+- id
+- target_branch
+- source_branch
+- source_project_id
+- author_id
+- assignee_id
+- title
+- created_at
+- updated_at
+- state
+- merge_status
+- target_project_id
+- iid
+- description
+- position
+- locked_at
+- updated_by_id
+- merge_error
+- merge_params
+- merge_when_build_succeeds
+- merge_user_id
+- merge_commit_sha
+- deleted_at
+- in_progress_merge_commit_sha
+- lock_version
+- milestone_id
+- approvals_before_merge
+- rebase_commit_sha
+MergeRequestDiff:
+- id
+- state
+- st_commits
+- merge_request_id
+- created_at
+- updated_at
+- base_commit_sha
+- real_size
+- head_commit_sha
+- start_commit_sha
+Ci::Pipeline:
+- id
+- project_id
+- ref
+- sha
+- before_sha
+- push_data
+- created_at
+- updated_at
+- tag
+- yaml_errors
+- committed_at
+- gl_project_id
+- status
+- started_at
+- finished_at
+- duration
+- user_id
+- lock_version
+CommitStatus:
+- id
+- project_id
+- status
+- finished_at
+- trace
+- created_at
+- updated_at
+- started_at
+- runner_id
+- coverage
+- commit_id
+- commands
+- job_id
+- name
+- deploy
+- options
+- allow_failure
+- stage
+- trigger_request_id
+- stage_idx
+- tag
+- ref
+- user_id
+- type
+- target_url
+- description
+- artifacts_file
+- gl_project_id
+- artifacts_metadata
+- erased_by_id
+- erased_at
+- artifacts_expire_at
+- environment
+- artifacts_size
+- when
+- yaml_variables
+- queued_at
+- token
+- lock_version
+Ci::Variable:
+- id
+- project_id
+- key
+- value
+- encrypted_value
+- encrypted_value_salt
+- encrypted_value_iv
+- gl_project_id
+Ci::Trigger:
+- id
+- token
+- project_id
+- deleted_at
+- created_at
+- updated_at
+- gl_project_id
+DeployKey:
+- id
+- user_id
+- created_at
+- updated_at
+- key
+- title
+- type
+- fingerprint
+- public
+Service:
+- id
+- type
+- title
+- project_id
+- created_at
+- updated_at
+- active
+- properties
+- template
+- push_events
+- issues_events
+- merge_requests_events
+- tag_push_events
+- note_events
+- pipeline_events
+- build_events
+- category
+- default
+- wiki_page_events
+- confidential_issues_events
+ProjectHook:
+- id
+- url
+- project_id
+- created_at
+- updated_at
+- type
+- service_id
+- push_events
+- issues_events
+- merge_requests_events
+- tag_push_events
+- note_events
+- pipeline_events
+- enable_ssl_verification
+- build_events
+- wiki_page_events
+- token
+- group_id
+- confidential_issues_events
+ProtectedBranch:
+- id
+- project_id
+- name
+- created_at
+- updated_at
+Project:
+- description
+- issues_enabled
+- merge_requests_enabled
+- wiki_enabled
+- snippets_enabled
+- visibility_level
+- archived
+Author:
+- name
+ProjectFeature:
+- id
+- project_id
+- merge_requests_access_level
+- issues_access_level
+- wiki_access_level
+- snippets_access_level
+- builds_access_level
+- repository_access_level
+- created_at
+- updated_at
+ProtectedBranch::MergeAccessLevel:
+- id
+- protected_branch_id
+- access_level
+- created_at
+- updated_at
+ProtectedBranch::PushAccessLevel:
+- id
+- protected_branch_id
+- access_level
+- created_at
+- updated_at
+AwardEmoji:
+- id
+- user_id
+- name
+- awardable_type
+- created_at
+- updated_at
+LabelPriority:
+- id
+- project_id
+- label_id
+- priority
+- created_at
+- updated_at \ No newline at end of file
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index 90c6d1c67f6..2405ac5abfe 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -1,8 +1,10 @@
require 'spec_helper'
+include ImportExport::CommonUtil
describe Gitlab::ImportExport::VersionChecker, services: true do
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
+
describe 'bundle a project Git repo' do
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
let(:version) { Gitlab::ImportExport.version }
before do
@@ -23,7 +25,19 @@ describe Gitlab::ImportExport::VersionChecker, services: true do
it 'shows the correct error message' do
described_class.check!(shared: shared)
- expect(shared.errors.first).to eq("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
+ expect(shared.errors.first).to eq("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
+ end
+ end
+ end
+
+ describe 'version file access check' do
+ it 'does not read a symlink' do
+ Dir.mktmpdir do |tmpdir|
+ setup_symlink(tmpdir, 'VERSION')
+
+ described_class.check!(shared: shared)
+
+ expect(shared.errors.first).not_to include('test')
end
end
end
diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb
index 4847b5f3b0e..563c074017a 100644
--- a/spec/lib/gitlab/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/ldap/adapter_spec.rb
@@ -1,24 +1,105 @@
require 'spec_helper'
describe Gitlab::LDAP::Adapter, lib: true do
- let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' }
+ include LdapHelpers
+
+ let(:ldap) { double(:ldap) }
+ let(:adapter) { ldap_adapter('ldapmain', ldap) }
+
+ describe '#users' do
+ before do
+ stub_ldap_config(base: 'dc=example,dc=com')
+ end
+
+ it 'searches with the proper options when searching by uid' do
+ # Requires this expectation style to match the filter
+ expect(adapter).to receive(:ldap_search) do |arg|
+ expect(arg[:filter].to_s).to eq('(uid=johndoe)')
+ expect(arg[:base]).to eq('dc=example,dc=com')
+ expect(arg[:attributes]).to match(%w{uid cn mail dn})
+ end.and_return({})
+
+ adapter.users('uid', 'johndoe')
+ end
+
+ it 'searches with the proper options when searching by dn' do
+ expect(adapter).to receive(:ldap_search).with(
+ base: 'uid=johndoe,ou=users,dc=example,dc=com',
+ scope: Net::LDAP::SearchScope_BaseObject,
+ attributes: %w{uid cn mail dn},
+ filter: nil
+ ).and_return({})
+
+ adapter.users('dn', 'uid=johndoe,ou=users,dc=example,dc=com')
+ end
+
+ it 'searches with the proper options when searching with a limit' do
+ expect(adapter)
+ .to receive(:ldap_search).with(hash_including(size: 100)).and_return({})
+
+ adapter.users('uid', 'johndoe', 100)
+ end
+
+ it 'returns an LDAP::Person if search returns a result' do
+ entry = ldap_user_entry('johndoe')
+ allow(adapter).to receive(:ldap_search).and_return([entry])
+
+ results = adapter.users('uid', 'johndoe')
+
+ expect(results.size).to eq(1)
+ expect(results.first.uid).to eq('johndoe')
+ end
+
+ it 'returns empty array if search entry does not respond to uid' do
+ entry = Net::LDAP::Entry.new
+ entry['dn'] = user_dn('johndoe')
+ allow(adapter).to receive(:ldap_search).and_return([entry])
+
+ results = adapter.users('uid', 'johndoe')
+
+ expect(results).to be_empty
+ end
+
+ it 'uses the right uid attribute when non-default' do
+ stub_ldap_config(uid: 'sAMAccountName')
+ expect(adapter).to receive(:ldap_search).with(
+ hash_including(attributes: %w{sAMAccountName cn mail dn})
+ ).and_return({})
+
+ adapter.users('sAMAccountName', 'johndoe')
+ end
+ end
describe '#dn_matches_filter?' do
- let(:ldap) { double(:ldap) }
subject { adapter.dn_matches_filter?(:dn, :filter) }
- before { allow(adapter).to receive(:ldap).and_return(ldap) }
+
+ context "when the search result is non-empty" do
+ before { allow(adapter).to receive(:ldap_search).and_return([:foo]) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context "when the search result is empty" do
+ before { allow(adapter).to receive(:ldap_search).and_return([]) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#ldap_search' do
+ subject { adapter.ldap_search(base: :dn, filter: :filter) }
context "when the search is successful" do
context "and the result is non-empty" do
before { allow(ldap).to receive(:search).and_return([:foo]) }
- it { is_expected.to be_truthy }
+ it { is_expected.to eq [:foo] }
end
context "and the result is empty" do
before { allow(ldap).to receive(:search).and_return([]) }
- it { is_expected.to be_falsey }
+ it { is_expected.to eq [] }
end
end
@@ -30,7 +111,22 @@ describe Gitlab::LDAP::Adapter, lib: true do
)
end
- it { is_expected.to be_falsey }
+ it { is_expected.to eq [] }
+ end
+
+ context "when the search raises an LDAP exception" do
+ before do
+ allow(ldap).to receive(:search) { raise Net::LDAP::Error, "some error" }
+ allow(Rails.logger).to receive(:warn)
+ end
+
+ it { is_expected.to eq [] }
+
+ it 'logs the error' do
+ subject
+ expect(Rails.logger).to have_received(:warn).with(
+ "LDAP search raised exception Net::LDAP::Error: some error")
+ end
end
end
end
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
index 835853a83a4..f5ebe703083 100644
--- a/spec/lib/gitlab/ldap/config_spec.rb
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -1,20 +1,51 @@
require 'spec_helper'
describe Gitlab::LDAP::Config, lib: true do
- let(:config) { Gitlab::LDAP::Config.new provider }
- let(:provider) { 'ldapmain' }
+ include LdapHelpers
+
+ let(:config) { Gitlab::LDAP::Config.new('ldapmain') }
describe '#initalize' do
it 'requires a provider' do
expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError
end
- it "works" do
+ it 'works' do
expect(config).to be_a described_class
end
- it "raises an error if a unknow provider is used" do
+ it 'raises an error if a unknown provider is used' do
expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error(RuntimeError)
end
end
+
+ describe '#has_auth?' do
+ it 'is true when password is set' do
+ stub_ldap_config(
+ options: {
+ 'bind_dn' => 'uid=admin,dc=example,dc=com',
+ 'password' => 'super_secret'
+ }
+ )
+
+ expect(config.has_auth?).to be_truthy
+ end
+
+ it 'is true when bind_dn is set and password is empty' do
+ stub_ldap_config(
+ options: {
+ 'bind_dn' => 'uid=admin,dc=example,dc=com',
+ 'password' => ''
+ }
+ )
+
+ expect(config.has_auth?).to be_truthy
+ end
+
+ it 'is false when password and bind_dn are not set' do
+ stub_ldap_config(options: { 'bind_dn' => nil, 'password' => nil })
+
+ expect(config.has_auth?).to be_falsey
+ end
+ end
end
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
new file mode 100644
index 00000000000..e9c1163e22a
--- /dev/null
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Gitlab::LfsToken, lib: true do
+ describe '#token' do
+ shared_examples 'an LFS token generator' do
+ it 'returns a randomly generated token' do
+ token = handler.token
+
+ expect(token).not_to be_nil
+ expect(token).to be_a String
+ expect(token.length).to eq 50
+ end
+
+ it 'returns the correct token based on the key' do
+ token = handler.token
+
+ expect(handler.token).to eq(token)
+ end
+ end
+
+ context 'when the actor is a user' do
+ let(:actor) { create(:user) }
+ let(:handler) { described_class.new(actor) }
+
+ it_behaves_like 'an LFS token generator'
+
+ it 'returns the correct username' do
+ expect(handler.actor_name).to eq(actor.username)
+ end
+
+ it 'returns the correct token type' do
+ expect(handler.type).to eq(:lfs_token)
+ end
+ end
+
+ context 'when the actor is a deploy key' do
+ let(:actor) { create(:deploy_key) }
+ let(:handler) { described_class.new(actor) }
+
+ it_behaves_like 'an LFS token generator'
+
+ it 'returns the correct username' do
+ expect(handler.actor_name).to eq("lfs+deploy-key-#{actor.id}")
+ end
+
+ it 'returns the correct token type' do
+ expect(handler.type).to eq(:lfs_deploy_token)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb
index f718d536130..f26fca52c50 100644
--- a/spec/lib/gitlab/metrics/metric_spec.rb
+++ b/spec/lib/gitlab/metrics/metric_spec.rb
@@ -23,6 +23,24 @@ describe Gitlab::Metrics::Metric do
it { is_expected.to eq({ host: 'localtoast' }) }
end
+ describe '#type' do
+ subject { metric.type }
+
+ it { is_expected.to eq(:metric) }
+ end
+
+ describe '#event?' do
+ it 'returns false for a regular metric' do
+ expect(metric.event?).to eq(false)
+ end
+
+ it 'returns true for an event metric' do
+ expect(metric).to receive(:type).and_return(:event)
+
+ expect(metric.event?).to eq(true)
+ end
+ end
+
describe '#to_hash' do
it 'returns a Hash' do
expect(metric.to_hash).to be_an_instance_of(Hash)
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index f264ed64029..bcaffd27909 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::Metrics::RackMiddleware do
end
it 'tags a transaction with the name and action of a controller' do
- klass = double(:klass, name: 'TestController')
+ klass = double(:klass, name: 'TestController', content_type: 'text/html')
controller = double(:controller, class: klass, action_name: 'show')
env['action_controller.instance'] = controller
@@ -32,7 +32,7 @@ describe Gitlab::Metrics::RackMiddleware do
middleware.call(env)
end
- it 'tags a transaction with the method andpath of the route in the grape endpoint' do
+ it 'tags a transaction with the method and path of the route in the grape endpoint' do
route = double(:route, route_method: "GET", route_path: "/:version/projects/:id/archive(.:format)")
endpoint = double(:endpoint, route: route)
@@ -45,6 +45,15 @@ describe Gitlab::Metrics::RackMiddleware do
middleware.call(env)
end
+
+ it 'tracks any raised exceptions' do
+ expect(app).to receive(:call).with(env).and_raise(RuntimeError)
+
+ expect_any_instance_of(Gitlab::Metrics::Transaction).
+ to receive(:add_event).with(:rails_exception)
+
+ expect { middleware.call(env) }.to raise_error(RuntimeError)
+ end
end
describe '#transaction_from_env' do
@@ -78,17 +87,30 @@ describe Gitlab::Metrics::RackMiddleware do
describe '#tag_controller' do
let(:transaction) { middleware.transaction_from_env(env) }
+ let(:content_type) { 'text/html' }
- it 'tags a transaction with the name and action of a controller' do
+ before do
klass = double(:klass, name: 'TestController')
- controller = double(:controller, class: klass, action_name: 'show')
+ controller = double(:controller, class: klass, action_name: 'show', content_type: content_type)
env['action_controller.instance'] = controller
+ end
+ it 'tags a transaction with the name and action of a controller' do
middleware.tag_controller(transaction, env)
expect(transaction.action).to eq('TestController#show')
end
+
+ context 'when the response content type is not :html' do
+ let(:content_type) { 'application/json' }
+
+ it 'appends the mime type to the transaction action' do
+ middleware.tag_controller(transaction, env)
+
+ expect(transaction.action).to eq('TestController#show.json')
+ end
+ end
end
describe '#tag_endpoint' do
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
index 4d2aa03e722..acaba785606 100644
--- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -12,7 +12,9 @@ describe Gitlab::Metrics::SidekiqMiddleware do
with('TestWorker#perform').
and_call_original
- expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).with(:sidekiq_queue_duration, instance_of(Float))
+ expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).
+ with(:sidekiq_queue_duration, instance_of(Float))
+
expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
middleware.call(worker, message, :test) { nil }
@@ -25,10 +27,28 @@ describe Gitlab::Metrics::SidekiqMiddleware do
with('TestWorker#perform').
and_call_original
- expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).with(:sidekiq_queue_duration, instance_of(Float))
+ expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).
+ with(:sidekiq_queue_duration, instance_of(Float))
+
expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
middleware.call(worker, {}, :test) { nil }
end
+
+ it 'tracks any raised exceptions' do
+ worker = double(:worker, class: double(:class, name: 'TestWorker'))
+
+ expect_any_instance_of(Gitlab::Metrics::Transaction).
+ to receive(:run).and_raise(RuntimeError)
+
+ expect_any_instance_of(Gitlab::Metrics::Transaction).
+ to receive(:add_event).with(:sidekiq_exception)
+
+ expect_any_instance_of(Gitlab::Metrics::Transaction).
+ to receive(:finish)
+
+ expect { middleware.call(worker, message, :test) }.
+ to raise_error(RuntimeError)
+ end
end
end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index f1a191d9410..3887c04c832 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -142,5 +142,62 @@ describe Gitlab::Metrics::Transaction do
transaction.submit
end
+
+ it 'does not add an action tag for events' do
+ transaction.action = 'Foo#bar'
+ transaction.add_event(:meow)
+
+ hash = {
+ series: 'events',
+ tags: { event: :meow },
+ values: { count: 1 },
+ timestamp: an_instance_of(Fixnum)
+ }
+
+ expect(Gitlab::Metrics).to receive(:submit_metrics).
+ with([hash])
+
+ transaction.submit
+ end
+ end
+
+ describe '#add_event' do
+ it 'adds a metric' do
+ transaction.add_event(:meow)
+
+ expect(transaction.metrics[0]).to be_an_instance_of(Gitlab::Metrics::Metric)
+ end
+
+ it "does not prefix the metric's series name" do
+ transaction.add_event(:meow)
+
+ metric = transaction.metrics[0]
+
+ expect(metric.series).to eq(described_class::EVENT_SERIES)
+ end
+
+ it 'tracks a counter for every event' do
+ transaction.add_event(:meow)
+
+ metric = transaction.metrics[0]
+
+ expect(metric.values).to eq(count: 1)
+ end
+
+ it 'tracks the event name' do
+ transaction.add_event(:meow)
+
+ metric = transaction.metrics[0]
+
+ expect(metric.tags).to eq(event: :meow)
+ end
+
+ it 'allows tracking of custom tags' do
+ transaction.add_event(:meow, animal: 'cat')
+
+ metric = transaction.metrics[0]
+
+ expect(metric.tags).to eq(event: :meow, animal: 'cat')
+ end
end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 84f9475a0f8..ab6e311b1e8 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -153,4 +153,28 @@ describe Gitlab::Metrics do
expect(described_class.series_prefix).to be_an_instance_of(String)
end
end
+
+ describe '.add_event' do
+ context 'without a transaction' do
+ it 'does nothing' do
+ expect_any_instance_of(Gitlab::Metrics::Transaction).
+ not_to receive(:add_event)
+
+ Gitlab::Metrics.add_event(:meow)
+ end
+ end
+
+ context 'with a transaction' do
+ it 'adds an event' do
+ transaction = Gitlab::Metrics::Transaction.new
+
+ expect(transaction).to receive(:add_event).with(:meow)
+
+ expect(Gitlab::Metrics).to receive(:current_transaction).
+ and_return(transaction)
+
+ Gitlab::Metrics.add_event(:meow)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
index fd6f684db0c..168090d5b5c 100644
--- a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
+++ b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
@@ -22,7 +22,7 @@ describe Gitlab::Middleware::RailsQueueDuration do
end
it 'sets proxy_flight_time and calls the app when the header is present' do
- env['HTTP_GITLAB_WORHORSE_PROXY_START'] = '123'
+ env['HTTP_GITLAB_WORKHORSE_PROXY_START'] = '123'
expect(transaction).to receive(:set).with(:rails_queue_duration, an_instance_of(Float))
expect(middleware.call(env)).to eq('yay')
end
diff --git a/spec/lib/gitlab/optimistic_locking_spec.rb b/spec/lib/gitlab/optimistic_locking_spec.rb
new file mode 100644
index 00000000000..498dc514c8c
--- /dev/null
+++ b/spec/lib/gitlab/optimistic_locking_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Gitlab::OptimisticLocking, lib: true do
+ describe '#retry_lock' do
+ let!(:pipeline) { create(:ci_pipeline) }
+ let!(:pipeline2) { Ci::Pipeline.find(pipeline.id) }
+
+ it 'does not reload object if state changes' do
+ expect(pipeline).not_to receive(:reload)
+ expect(pipeline).to receive(:succeed).and_call_original
+
+ described_class.retry_lock(pipeline) do |subject|
+ subject.succeed
+ end
+ end
+
+ it 'retries action if exception is raised' do
+ pipeline.succeed
+
+ expect(pipeline2).to receive(:reload).and_call_original
+ expect(pipeline2).to receive(:drop).twice.and_call_original
+
+ described_class.retry_lock(pipeline2) do |subject|
+ subject.drop
+ end
+ end
+
+ it 'raises exception when too many retries' do
+ expect(pipeline).to receive(:drop).twice.and_call_original
+
+ expect do
+ described_class.retry_lock(pipeline, 1) do |subject|
+ subject.lock_version = 100
+ subject.drop
+ end
+ end.to raise_error(ActiveRecord::StaleObjectError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index e8b236426e9..4ae216d55b0 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -40,4 +40,13 @@ describe 'Gitlab::Popen', lib: true, no_db: true do
it { expect(@status).to be_zero }
it { expect(@output).to include('spec') }
end
+
+ context 'use stdin' do
+ before do
+ @output, @status = @klass.new.popen(%w[cat]) { |stdin| stdin.write 'hello' }
+ end
+
+ it { expect(@status).to be_zero }
+ it { expect(@output).to eq('hello') }
+ end
end
diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb
index e54f5ffb312..e5406fb2d33 100644
--- a/spec/lib/gitlab/redis_spec.rb
+++ b/spec/lib/gitlab/redis_spec.rb
@@ -3,19 +3,27 @@ require 'spec_helper'
describe Gitlab::Redis do
let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s }
- before(:each) { described_class.reset_params! }
- after(:each) { described_class.reset_params! }
+ before(:each) { clear_raw_config }
+ after(:each) { clear_raw_config }
describe '.params' do
subject { described_class.params }
+ it 'withstands mutation' do
+ params1 = described_class.params
+ params2 = described_class.params
+ params1[:foo] = :bar
+
+ expect(params2).not_to have_key(:foo)
+ end
+
context 'when url contains unix socket reference' do
let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_socket.yml').to_s }
let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_socket.yml').to_s }
context 'with old format' do
it 'returns path key instead' do
- expect_any_instance_of(described_class).to receive(:config_file) { config_old }
+ stub_const("#{described_class}::CONFIG_FILE", config_old)
is_expected.to include(path: '/path/to/old/redis.sock')
is_expected.not_to have_key(:url)
@@ -24,7 +32,7 @@ describe Gitlab::Redis do
context 'with new format' do
it 'returns path key instead' do
- expect_any_instance_of(described_class).to receive(:config_file) { config_new }
+ stub_const("#{described_class}::CONFIG_FILE", config_new)
is_expected.to include(path: '/path/to/redis.sock')
is_expected.not_to have_key(:url)
@@ -38,7 +46,7 @@ describe Gitlab::Redis do
context 'with old format' do
it 'returns hash with host, port, db, and password' do
- expect_any_instance_of(described_class).to receive(:config_file) { config_old }
+ stub_const("#{described_class}::CONFIG_FILE", config_old)
is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
@@ -47,7 +55,7 @@ describe Gitlab::Redis do
context 'with new format' do
it 'returns hash with host, port, db, and password' do
- expect_any_instance_of(described_class).to receive(:config_file) { config_new }
+ stub_const("#{described_class}::CONFIG_FILE", config_new)
is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
@@ -56,6 +64,107 @@ describe Gitlab::Redis do
end
end
+ describe '.url' do
+ it 'withstands mutation' do
+ url1 = described_class.url
+ url2 = described_class.url
+ url1 << 'foobar'
+
+ expect(url2).not_to end_with('foobar')
+ end
+ end
+
+ describe '._raw_config' do
+ subject { described_class._raw_config }
+
+ it 'should be frozen' do
+ expect(subject).to be_frozen
+ end
+
+ it 'returns false when the file does not exist' do
+ stub_const("#{described_class}::CONFIG_FILE", '/var/empty/doesnotexist')
+
+ expect(subject).to eq(false)
+ end
+ end
+
+ describe '.with' do
+ before { clear_pool }
+ after { clear_pool }
+
+ context 'when running not on sidekiq workers' do
+ before { allow(Sidekiq).to receive(:server?).and_return(false) }
+
+ it 'instantiates a connection pool with size 5' do
+ expect(ConnectionPool).to receive(:new).with(size: 5).and_call_original
+
+ described_class.with { |_redis| true }
+ end
+ end
+
+ context 'when running on sidekiq workers' do
+ before do
+ allow(Sidekiq).to receive(:server?).and_return(true)
+ allow(Sidekiq).to receive(:options).and_return({ concurrency: 18 })
+ end
+
+ it 'instantiates a connection pool with a size based on the concurrency of the worker' do
+ expect(ConnectionPool).to receive(:new).with(size: 18 + 5).and_call_original
+
+ described_class.with { |_redis| true }
+ end
+ end
+ end
+
+ describe '#sentinels' do
+ subject { described_class.new(Rails.env).sentinels }
+
+ context 'when sentinels are defined' do
+ let(:config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
+
+ it 'returns an array of hashes with host and port keys' do
+ stub_const("#{described_class}::CONFIG_FILE", config)
+
+ is_expected.to include(host: 'localhost', port: 26380)
+ is_expected.to include(host: 'slave2', port: 26381)
+ end
+ end
+
+ context 'when sentinels are not defined' do
+ let(:config) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') }
+
+ it 'returns nil' do
+ stub_const("#{described_class}::CONFIG_FILE", config)
+
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#sentinels?' do
+ subject { described_class.new(Rails.env).sentinels? }
+
+ context 'when sentinels are defined' do
+ let(:config) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
+
+ it 'returns true' do
+ stub_const("#{described_class}::CONFIG_FILE", config)
+
+ is_expected.to be_truthy
+ end
+ end
+
+ context 'when sentinels are not defined' do
+ let(:config) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') }
+
+ it 'returns false' do
+ stub_const("#{described_class}::CONFIG_FILE", config)
+
+ is_expected.to be_falsey
+ end
+ end
+ end
+
describe '#raw_config_hash' do
it 'returns default redis url when no config file is present' do
expect(subject).to receive(:fetch_config) { false }
@@ -71,9 +180,21 @@ describe Gitlab::Redis do
describe '#fetch_config' do
it 'returns false when no config file is present' do
- allow(File).to receive(:exist?).with(redis_config) { false }
+ allow(described_class).to receive(:_raw_config) { false }
expect(subject.send(:fetch_config)).to be_falsey
end
end
+
+ def clear_raw_config
+ described_class.remove_instance_variable(:@_raw_config)
+ rescue NameError
+ # raised if @_raw_config was not set; ignore
+ end
+
+ def clear_pool
+ described_class.remove_instance_variable(:@pool)
+ rescue NameError
+ # raised if @pool was not set; ignore
+ end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 7b4ccc83915..bf0ab9635fd 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -3,6 +3,8 @@ require 'spec_helper'
describe Gitlab::ReferenceExtractor, lib: true do
let(:project) { create(:project) }
+ before { project.team << [project.creator, :developer] }
+
subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
it 'accesses valid user objects' do
@@ -42,7 +44,6 @@ describe Gitlab::ReferenceExtractor, lib: true do
end
it 'accesses valid issue objects' do
- project.team << [project.creator, :developer]
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 8a656ab0ee9..dfbefad6367 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -12,12 +12,6 @@ describe Gitlab::SearchResults do
let!(:milestone) { create(:milestone, project: project, title: 'foo') }
let(:results) { described_class.new(user, Project.all, 'foo') }
- describe '#total_count' do
- it 'returns the total amount of search hits' do
- expect(results.total_count).to eq(4)
- end
- end
-
describe '#projects_count' do
it 'returns the total amount of projects' do
expect(results.projects_count).to eq(1)
@@ -42,18 +36,6 @@ describe Gitlab::SearchResults do
end
end
- describe '#empty?' do
- it 'returns true when there are no search results' do
- allow(results).to receive(:total_count).and_return(0)
-
- expect(results.empty?).to eq(true)
- end
-
- it 'returns false when there are search results' do
- expect(results.empty?).to eq(false)
- end
- end
-
describe 'confidential issues' do
let(:project_1) { create(:empty_project) }
let(:project_2) { create(:empty_project) }
diff --git a/spec/lib/gitlab/slash_commands/command_definition_spec.rb b/spec/lib/gitlab/slash_commands/command_definition_spec.rb
new file mode 100644
index 00000000000..c9c2f314e57
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/command_definition_spec.rb
@@ -0,0 +1,173 @@
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::CommandDefinition do
+ subject { described_class.new(:command) }
+
+ describe "#all_names" do
+ context "when the command has aliases" do
+ before do
+ subject.aliases = [:alias1, :alias2]
+ end
+
+ it "returns an array with the name and aliases" do
+ expect(subject.all_names).to eq([:command, :alias1, :alias2])
+ end
+ end
+
+ context "when the command doesn't have aliases" do
+ it "returns an array with the name" do
+ expect(subject.all_names).to eq([:command])
+ end
+ end
+ end
+
+ describe "#noop?" do
+ context "when the command has an action block" do
+ before do
+ subject.action_block = proc { }
+ end
+
+ it "returns false" do
+ expect(subject.noop?).to be false
+ end
+ end
+
+ context "when the command doesn't have an action block" do
+ it "returns true" do
+ expect(subject.noop?).to be true
+ end
+ end
+ end
+
+ describe "#available?" do
+ let(:opts) { { go: false } }
+
+ context "when the command has a condition block" do
+ before do
+ subject.condition_block = proc { go }
+ end
+
+ context "when the condition block returns true" do
+ before do
+ opts[:go] = true
+ end
+
+ it "returns true" do
+ expect(subject.available?(opts)).to be true
+ end
+ end
+
+ context "when the condition block returns false" do
+ it "returns false" do
+ expect(subject.available?(opts)).to be false
+ end
+ end
+ end
+
+ context "when the command doesn't have a condition block" do
+ it "returns true" do
+ expect(subject.available?(opts)).to be true
+ end
+ end
+ end
+
+ describe "#execute" do
+ let(:context) { OpenStruct.new(run: false) }
+
+ context "when the command is a noop" do
+ it "doesn't execute the command" do
+ expect(context).not_to receive(:instance_exec)
+
+ subject.execute(context, {}, nil)
+
+ expect(context.run).to be false
+ end
+ end
+
+ context "when the command is not a noop" do
+ before do
+ subject.action_block = proc { self.run = true }
+ end
+
+ context "when the command is not available" do
+ before do
+ subject.condition_block = proc { false }
+ end
+
+ it "doesn't execute the command" do
+ subject.execute(context, {}, nil)
+
+ expect(context.run).to be false
+ end
+ end
+
+ context "when the command is available" do
+ context "when the commnd has no arguments" do
+ before do
+ subject.action_block = proc { self.run = true }
+ end
+
+ context "when the command is provided an argument" do
+ it "executes the command" do
+ subject.execute(context, {}, true)
+
+ expect(context.run).to be true
+ end
+ end
+
+ context "when the command is not provided an argument" do
+ it "executes the command" do
+ subject.execute(context, {}, nil)
+
+ expect(context.run).to be true
+ end
+ end
+ end
+
+ context "when the command has 1 required argument" do
+ before do
+ subject.action_block = ->(arg) { self.run = arg }
+ end
+
+ context "when the command is provided an argument" do
+ it "executes the command" do
+ subject.execute(context, {}, true)
+
+ expect(context.run).to be true
+ end
+ end
+
+ context "when the command is not provided an argument" do
+ it "doesn't execute the command" do
+ subject.execute(context, {}, nil)
+
+ expect(context.run).to be false
+ end
+ end
+ end
+
+ context "when the command has 1 optional argument" do
+ before do
+ subject.action_block = proc { |arg = nil| self.run = arg || true }
+ end
+
+ context "when the command is provided an argument" do
+ it "executes the command" do
+ subject.execute(context, {}, true)
+
+ expect(context.run).to be true
+ end
+ end
+
+ context "when the command is not provided an argument" do
+ it "executes the command" do
+ subject.execute(context, {}, nil)
+
+ expect(context.run).to be true
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/dsl_spec.rb b/spec/lib/gitlab/slash_commands/dsl_spec.rb
new file mode 100644
index 00000000000..26217a0e3b2
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/dsl_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Dsl do
+ before :all do
+ DummyClass = Struct.new(:project) do
+ include Gitlab::SlashCommands::Dsl
+
+ desc 'A command with no args'
+ command :no_args, :none do
+ "Hello World!"
+ end
+
+ params 'The first argument'
+ command :one_arg, :once, :first do |arg1|
+ arg1
+ end
+
+ desc do
+ "A dynamic description for #{noteable.upcase}"
+ end
+ params 'The first argument', 'The second argument'
+ command :two_args do |arg1, arg2|
+ [arg1, arg2]
+ end
+
+ command :cc
+
+ condition do
+ project == 'foo'
+ end
+ command :cond_action do |arg|
+ arg
+ end
+ end
+ end
+
+ describe '.command_definitions' do
+ it 'returns an array with commands definitions' do
+ no_args_def, one_arg_def, two_args_def, cc_def, cond_action_def = DummyClass.command_definitions
+
+ expect(no_args_def.name).to eq(:no_args)
+ expect(no_args_def.aliases).to eq([:none])
+ expect(no_args_def.description).to eq('A command with no args')
+ expect(no_args_def.params).to eq([])
+ expect(no_args_def.condition_block).to be_nil
+ expect(no_args_def.action_block).to be_a_kind_of(Proc)
+
+ expect(one_arg_def.name).to eq(:one_arg)
+ expect(one_arg_def.aliases).to eq([:once, :first])
+ expect(one_arg_def.description).to eq('')
+ expect(one_arg_def.params).to eq(['The first argument'])
+ expect(one_arg_def.condition_block).to be_nil
+ expect(one_arg_def.action_block).to be_a_kind_of(Proc)
+
+ expect(two_args_def.name).to eq(:two_args)
+ expect(two_args_def.aliases).to eq([])
+ expect(two_args_def.to_h(noteable: "issue")[:description]).to eq('A dynamic description for ISSUE')
+ expect(two_args_def.params).to eq(['The first argument', 'The second argument'])
+ expect(two_args_def.condition_block).to be_nil
+ expect(two_args_def.action_block).to be_a_kind_of(Proc)
+
+ expect(cc_def.name).to eq(:cc)
+ expect(cc_def.aliases).to eq([])
+ expect(cc_def.description).to eq('')
+ expect(cc_def.params).to eq([])
+ expect(cc_def.condition_block).to be_nil
+ expect(cc_def.action_block).to be_nil
+
+ expect(cond_action_def.name).to eq(:cond_action)
+ expect(cond_action_def.aliases).to eq([])
+ expect(cond_action_def.description).to eq('')
+ expect(cond_action_def.params).to eq([])
+ expect(cond_action_def.condition_block).to be_a_kind_of(Proc)
+ expect(cond_action_def.action_block).to be_a_kind_of(Proc)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/extractor_spec.rb b/spec/lib/gitlab/slash_commands/extractor_spec.rb
new file mode 100644
index 00000000000..1e4954c4af8
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/extractor_spec.rb
@@ -0,0 +1,215 @@
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Extractor do
+ let(:definitions) do
+ Class.new do
+ include Gitlab::SlashCommands::Dsl
+
+ command(:reopen, :open) { }
+ command(:assign) { }
+ command(:labels) { }
+ command(:power) { }
+ end.command_definitions
+ end
+
+ let(:extractor) { described_class.new(definitions) }
+
+ shared_examples 'command with no argument' do
+ it 'extracts command' do
+ msg, commands = extractor.extract_commands(original_msg)
+
+ expect(commands).to eq [['reopen']]
+ expect(msg).to eq final_msg
+ end
+ end
+
+ shared_examples 'command with a single argument' do
+ it 'extracts command' do
+ msg, commands = extractor.extract_commands(original_msg)
+
+ expect(commands).to eq [['assign', '@joe']]
+ expect(msg).to eq final_msg
+ end
+ end
+
+ shared_examples 'command with multiple arguments' do
+ it 'extracts command' do
+ msg, commands = extractor.extract_commands(original_msg)
+
+ expect(commands).to eq [['labels', '~foo ~"bar baz" label']]
+ expect(msg).to eq final_msg
+ end
+ end
+
+ describe '#extract_commands' do
+ describe 'command with no argument' do
+ context 'at the start of content' do
+ it_behaves_like 'command with no argument' do
+ let(:original_msg) { "/reopen\nworld" }
+ let(:final_msg) { "world" }
+ end
+ end
+
+ context 'in the middle of content' do
+ it_behaves_like 'command with no argument' do
+ let(:original_msg) { "hello\n/reopen\nworld" }
+ let(:final_msg) { "hello\nworld" }
+ end
+ end
+
+ context 'in the middle of a line' do
+ it 'does not extract command' do
+ msg = "hello\nworld /reopen"
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq "hello\nworld /reopen"
+ end
+ end
+
+ context 'at the end of content' do
+ it_behaves_like 'command with no argument' do
+ let(:original_msg) { "hello\n/reopen" }
+ let(:final_msg) { "hello" }
+ end
+ end
+ end
+
+ describe 'command with a single argument' do
+ context 'at the start of content' do
+ it_behaves_like 'command with a single argument' do
+ let(:original_msg) { "/assign @joe\nworld" }
+ let(:final_msg) { "world" }
+ end
+ end
+
+ context 'in the middle of content' do
+ it_behaves_like 'command with a single argument' do
+ let(:original_msg) { "hello\n/assign @joe\nworld" }
+ let(:final_msg) { "hello\nworld" }
+ end
+ end
+
+ context 'in the middle of a line' do
+ it 'does not extract command' do
+ msg = "hello\nworld /assign @joe"
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq "hello\nworld /assign @joe"
+ end
+ end
+
+ context 'at the end of content' do
+ it_behaves_like 'command with a single argument' do
+ let(:original_msg) { "hello\n/assign @joe" }
+ let(:final_msg) { "hello" }
+ end
+ end
+
+ context 'when argument is not separated with a space' do
+ it 'does not extract command' do
+ msg = "hello\n/assign@joe\nworld"
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq "hello\n/assign@joe\nworld"
+ end
+ end
+ end
+
+ describe 'command with multiple arguments' do
+ context 'at the start of content' do
+ it_behaves_like 'command with multiple arguments' do
+ let(:original_msg) { %(/labels ~foo ~"bar baz" label\nworld) }
+ let(:final_msg) { "world" }
+ end
+ end
+
+ context 'in the middle of content' do
+ it_behaves_like 'command with multiple arguments' do
+ let(:original_msg) { %(hello\n/labels ~foo ~"bar baz" label\nworld) }
+ let(:final_msg) { "hello\nworld" }
+ end
+ end
+
+ context 'in the middle of a line' do
+ it 'does not extract command' do
+ msg = %(hello\nworld /labels ~foo ~"bar baz" label)
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq %(hello\nworld /labels ~foo ~"bar baz" label)
+ end
+ end
+
+ context 'at the end of content' do
+ it_behaves_like 'command with multiple arguments' do
+ let(:original_msg) { %(hello\n/labels ~foo ~"bar baz" label) }
+ let(:final_msg) { "hello" }
+ end
+ end
+
+ context 'when argument is not separated with a space' do
+ it 'does not extract command' do
+ msg = %(hello\n/labels~foo ~"bar baz" label\nworld)
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq %(hello\n/labels~foo ~"bar baz" label\nworld)
+ end
+ end
+ end
+
+ it 'extracts command with multiple arguments and various prefixes' do
+ msg = %(hello\n/power @user.name %9.10 ~"bar baz.2"\nworld)
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to eq [['power', '@user.name %9.10 ~"bar baz.2"']]
+ expect(msg).to eq "hello\nworld"
+ end
+
+ it 'extracts multiple commands' do
+ msg = %(hello\n/power @user.name %9.10 ~"bar baz.2" label\nworld\n/reopen)
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to eq [['power', '@user.name %9.10 ~"bar baz.2" label'], ['reopen']]
+ expect(msg).to eq "hello\nworld"
+ end
+
+ it 'does not alter original content if no command is found' do
+ msg = 'Fixes #123'
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq 'Fixes #123'
+ end
+
+ it 'does not extract commands inside a blockcode' do
+ msg = "Hello\r\n```\r\nThis is some text\r\n/close\r\n/assign @user\r\n```\r\n\r\nWorld"
+ expected = msg.delete("\r")
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq expected
+ end
+
+ it 'does not extract commands inside a blockquote' do
+ msg = "Hello\r\n>>>\r\nThis is some text\r\n/close\r\n/assign @user\r\n>>>\r\n\r\nWorld"
+ expected = msg.delete("\r")
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq expected
+ end
+
+ it 'does not extract commands inside a HTML tag' do
+ msg = "Hello\r\n<div>\r\nThis is some text\r\n/close\r\n/assign @user\r\n</div>\r\n\r\nWorld"
+ expected = msg.delete("\r")
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq expected
+ end
+ end
+end
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index e86b9ef6a63..b661a894c0c 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -5,12 +5,6 @@ describe Gitlab::SnippetSearchResults do
let(:results) { described_class.new(Snippet.all, 'foo') }
- describe '#total_count' do
- it 'returns the total amount of search hits' do
- expect(results.total_count).to eq(2)
- end
- end
-
describe '#snippet_titles_count' do
it 'returns the amount of matched snippet titles' do
expect(results.snippet_titles_count).to eq(1)
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index f770857e958..d2d334e6413 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -10,7 +10,7 @@ describe Gitlab::Template::IssueTemplate do
let(:file_path_3) { '.gitlab/issue_templates/feature_proposal.md' }
before do
- project.team.add_user(user, Gitlab::Access::MASTER)
+ project.add_user(user, Gitlab::Access::MASTER)
project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false)
project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false)
project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false)
@@ -53,7 +53,7 @@ describe Gitlab::Template::IssueTemplate do
context 'when repo is bare or empty' do
let(:empty_project) { create(:empty_project) }
- before { empty_project.team.add_user(user, Gitlab::Access::MASTER) }
+ before { empty_project.add_user(user, Gitlab::Access::MASTER) }
it "returns empty array" do
templates = subject.by_category('', empty_project)
@@ -78,7 +78,7 @@ describe Gitlab::Template::IssueTemplate do
context "when repo is empty" do
let(:empty_project) { create(:empty_project) }
- before { empty_project.team.add_user(user, Gitlab::Access::MASTER) }
+ before { empty_project.add_user(user, Gitlab::Access::MASTER) }
it "raises file not found" do
issue_template = subject.new('.gitlab/issue_templates/not_existent.md', empty_project)
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index bb0f68043fa..ddf68c4cf78 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -10,7 +10,7 @@ describe Gitlab::Template::MergeRequestTemplate do
let(:file_path_3) { '.gitlab/merge_request_templates/feature_proposal.md' }
before do
- project.team.add_user(user, Gitlab::Access::MASTER)
+ project.add_user(user, Gitlab::Access::MASTER)
project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false)
project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false)
project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false)
@@ -53,7 +53,7 @@ describe Gitlab::Template::MergeRequestTemplate do
context 'when repo is bare or empty' do
let(:empty_project) { create(:empty_project) }
- before { empty_project.team.add_user(user, Gitlab::Access::MASTER) }
+ before { empty_project.add_user(user, Gitlab::Access::MASTER) }
it "returns empty array" do
templates = subject.by_category('', empty_project)
@@ -78,7 +78,7 @@ describe Gitlab::Template::MergeRequestTemplate do
context "when repo is empty" do
let(:empty_project) { create(:empty_project) }
- before { empty_project.team.add_user(user, Gitlab::Access::MASTER) }
+ before { empty_project.add_user(user, Gitlab::Access::MASTER) }
it "raises file not found" do
issue_template = subject.new('.gitlab/merge_request_templates/not_existent.md', empty_project)
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
new file mode 100644
index 00000000000..d5d87310874
--- /dev/null
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -0,0 +1,35 @@
+describe Gitlab::Utils, lib: true do
+ def to_boolean(value)
+ described_class.to_boolean(value)
+ end
+
+ describe '.to_boolean' do
+ it 'accepts booleans' do
+ expect(to_boolean(true)).to be(true)
+ expect(to_boolean(false)).to be(false)
+ end
+
+ it 'converts a valid string to a boolean' do
+ expect(to_boolean(true)).to be(true)
+ expect(to_boolean('true')).to be(true)
+ expect(to_boolean('YeS')).to be(true)
+ expect(to_boolean('t')).to be(true)
+ expect(to_boolean('1')).to be(true)
+ expect(to_boolean('ON')).to be(true)
+
+ expect(to_boolean('FaLse')).to be(false)
+ expect(to_boolean('F')).to be(false)
+ expect(to_boolean('NO')).to be(false)
+ expect(to_boolean('n')).to be(false)
+ expect(to_boolean('0')).to be(false)
+ expect(to_boolean('oFF')).to be(false)
+ end
+
+ it 'converts an invalid string to nil' do
+ expect(to_boolean('fals')).to be_nil
+ expect(to_boolean('yeah')).to be_nil
+ expect(to_boolean('')).to be_nil
+ expect(to_boolean(nil)).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index c5c1402e8fc..b5b685da904 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -1,18 +1,141 @@
require 'spec_helper'
describe Gitlab::Workhorse, lib: true do
- let(:project) { create(:project) }
- let(:subject) { Gitlab::Workhorse }
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
- describe "#send_git_archive" do
+ def decode_workhorse_header(array)
+ key, value = array
+ command, encoded_params = value.split(":")
+ params = JSON.parse(Base64.urlsafe_decode64(encoded_params))
+
+ [key, command, params]
+ end
+
+ describe ".send_git_archive" do
context "when the repository doesn't have an archive file path" do
before do
allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
end
it "raises an error" do
- expect { subject.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError)
+ expect { described_class.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError)
+ end
+ end
+ end
+
+ describe '.send_git_patch' do
+ let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
+ subject { described_class.send_git_patch(repository, diff_refs) }
+
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("git-format-patch")
+ expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
+ end
+ end
+
+ describe '.send_git_diff' do
+ let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
+ subject { described_class.send_git_patch(repository, diff_refs) }
+
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("git-format-patch")
+ expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
+ end
+ end
+
+ describe ".secret" do
+ subject { described_class.secret }
+
+ before do
+ described_class.instance_variable_set(:@secret, nil)
+ described_class.write_secret
+ end
+
+ it 'returns 32 bytes' do
+ expect(subject).to be_a(String)
+ expect(subject.length).to eq(32)
+ expect(subject.encoding).to eq(Encoding::ASCII_8BIT)
+ end
+
+ it 'accepts a trailing newline' do
+ open(described_class.secret_path, 'a') { |f| f.write "\n" }
+ expect(subject.length).to eq(32)
+ end
+
+ it 'raises an exception if the secret file cannot be read' do
+ File.delete(described_class.secret_path)
+ expect { subject }.to raise_exception(Errno::ENOENT)
+ end
+
+ it 'raises an exception if the secret file contains the wrong number of bytes' do
+ File.truncate(described_class.secret_path, 0)
+ expect { subject }.to raise_exception(RuntimeError)
+ end
+ end
+
+ describe ".write_secret" do
+ let(:secret_path) { described_class.secret_path }
+ before do
+ begin
+ File.delete(secret_path)
+ rescue Errno::ENOENT
end
+
+ described_class.write_secret
+ end
+
+ it 'uses mode 0600' do
+ expect(File.stat(secret_path).mode & 0777).to eq(0600)
+ end
+
+ it 'writes base64 data' do
+ bytes = Base64.strict_decode64(File.read(secret_path))
+ expect(bytes).not_to be_empty
+ end
+ end
+
+ describe '#verify_api_request!' do
+ let(:header_key) { described_class::INTERNAL_API_REQUEST_HEADER }
+ let(:payload) { { 'iss' => 'gitlab-workhorse' } }
+
+ it 'accepts a correct header' do
+ headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') }
+ expect { call_verify(headers) }.not_to raise_error
+ end
+
+ it 'raises an error when the header is not set' do
+ expect { call_verify({}) }.to raise_jwt_error
+ end
+
+ it 'raises an error when the header is not signed' do
+ headers = { header_key => JWT.encode(payload, nil, 'none') }
+ expect { call_verify(headers) }.to raise_jwt_error
+ end
+
+ it 'raises an error when the header is signed with the wrong key' do
+ headers = { header_key => JWT.encode(payload, 'wrongkey', 'HS256') }
+ expect { call_verify(headers) }.to raise_jwt_error
+ end
+
+ it 'raises an error when the issuer is incorrect' do
+ payload['iss'] = 'somebody else'
+ headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') }
+ expect { call_verify(headers) }.to raise_jwt_error
+ end
+
+ def raise_jwt_error
+ raise_error(JWT::DecodeError)
+ end
+
+ def call_verify(headers)
+ described_class.verify_api_request!(headers)
end
end
end