summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb11
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb6
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb15
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb32
-rw-r--r--spec/features/groups/merge_requests_spec.rb2
-rw-r--r--spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb22
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb8
-rw-r--r--spec/features/merge_requests/widget_spec.rb18
-rw-r--r--spec/features/milestones/show_spec.rb4
-rw-r--r--spec/features/profiles/user_visits_profile_account_page_spec.rb15
-rw-r--r--spec/features/profiles/user_visits_profile_authentication_log_spec.rb15
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb (renamed from spec/features/profiles/preferences_spec.rb)13
-rw-r--r--spec/features/profiles/user_visits_profile_spec.rb15
-rw-r--r--spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb15
-rw-r--r--spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb104
-rw-r--r--spec/features/projects/commit/user_reverts_commit_spec.rb56
-rw-r--r--spec/features/projects/diffs/diff_show_spec.rb13
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb37
-rw-r--r--spec/features/projects/jobs/user_browses_jobs_spec.rb32
-rw-r--r--spec/features/projects/jobs_spec.rb6
-rw-r--r--spec/features/projects/snippets/user_comments_on_snippet_spec.rb25
-rw-r--r--spec/features/projects/snippets/user_deletes_snippet_spec.rb20
-rw-r--r--spec/features/projects/snippets/user_updates_snippet_spec.rb25
-rw-r--r--spec/features/projects/snippets/user_views_snippets_spec.rb20
-rw-r--r--spec/features/projects/user_archives_project_spec.rb43
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb67
-rw-r--r--spec/features/search/user_searches_for_comments_spec.rb47
-rw-r--r--spec/features/search/user_searches_for_commits_spec.rb49
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb76
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb51
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb51
-rw-r--r--spec/features/search/user_searches_for_projects_spec.rb36
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb35
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb90
-rw-r--r--spec/features/search/user_uses_search_filters_spec.rb52
-rw-r--r--spec/features/search_spec.rb310
-rw-r--r--spec/finders/autocomplete_users_finder_spec.rb97
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/issues.json1
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/user/login.json1
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb26
-rw-r--r--spec/helpers/diff_helper_spec.rb4
-rw-r--r--spec/helpers/preferences_helper_spec.rb30
-rw-r--r--spec/helpers/projects_helper_spec.rb11
-rw-r--r--spec/initializers/doorkeeper_spec.rb4
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js2
-rw-r--r--spec/javascripts/fly_out_nav_spec.js9
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js20
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js58
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js14
-rw-r--r--spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js34
-rw-r--r--spec/lib/banzai/pipeline/email_pipeline_spec.rb14
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb1697
-rw-r--r--spec/lib/gitlab/auth_spec.rb42
-rw-r--r--spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb40
-rw-r--r--spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb (renamed from spec/lib/ci/ansi2html_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/charts_spec.rb (renamed from spec/lib/ci/charts_spec.rb)4
-rw-r--r--spec/lib/gitlab/ci/mask_secret_spec.rb (renamed from spec/lib/ci/mask_secret_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb1699
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb2
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb122
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb10
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb10
-rw-r--r--spec/lib/gitlab/git/hooks_service_spec.rb8
-rw-r--r--spec/lib/gitlab/git/user_spec.rb (renamed from spec/lib/gitlab/git/committer_spec.rb)2
-rw-r--r--spec/lib/gitlab/git_spec.rb9
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb25
-rw-r--r--spec/lib/gitlab/group_hierarchy_spec.rb15
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/o_auth/auth_hash_spec.rb7
-rw-r--r--spec/lib/gitlab/themes_spec.rb48
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb4
-rw-r--r--spec/lib/system_check/orphans/namespace_check_spec.rb61
-rw-r--r--spec/lib/system_check/orphans/repository_check_spec.rb68
-rw-r--r--spec/migrations/convert_custom_notification_settings_to_columns_spec.rb6
-rw-r--r--spec/migrations/delete_conflicting_redirect_routes_spec.rb58
-rw-r--r--spec/models/abuse_report_spec.rb7
-rw-r--r--spec/models/appearance_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb22
-rw-r--r--spec/models/chat_name_spec.rb3
-rw-r--r--spec/models/chat_team_spec.rb3
-rw-r--r--spec/models/ci/build_spec.rb48
-rw-r--r--spec/models/ci/pipeline_spec.rb20
-rw-r--r--spec/models/commit_spec.rb7
-rw-r--r--spec/models/gpg_key_spec.rb12
-rw-r--r--spec/models/key_spec.rb12
-rw-r--r--spec/models/lfs_objects_project_spec.rb15
-rw-r--r--spec/models/personal_access_token_spec.rb35
-rw-r--r--spec/models/project_services/pipelines_email_service_spec.rb37
-rw-r--r--spec/models/project_spec.rb56
-rw-r--r--spec/models/repository_spec.rb46
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/requests/api/issues_spec.rb11
-rw-r--r--spec/requests/api/merge_requests_spec.rb37
-rw-r--r--spec/requests/api/projects_spec.rb6
-rw-r--r--spec/requests/api/services_spec.rb13
-rw-r--r--spec/requests/jwt_controller_spec.rb4
-rw-r--r--spec/services/ci/pipeline_trigger_service_spec.rb2
-rw-r--r--spec/services/ci/retry_build_service_spec.rb9
-rw-r--r--spec/services/deploy_keys/create_service_spec.rb12
-rw-r--r--spec/services/gpg_keys/create_service_spec.rb21
-rw-r--r--spec/services/keys/create_service_spec.rb21
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb4
-rw-r--r--spec/services/notification_service_spec.rb1
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb2
-rw-r--r--spec/services/projects/update_service_spec.rb23
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/api/scopes/read_user_shared_examples.rb4
-rw-r--r--spec/support/gitlab_stubs/session.json2
-rw-r--r--spec/support/gitlab_stubs/user.json4
-rw-r--r--spec/support/matchers/navigation_matcher.rb6
-rw-r--r--spec/support/query_recorder.rb36
-rw-r--r--spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb (renamed from spec/support/project_features_apply_to_issuables_shared_examples.rb)0
-rw-r--r--spec/support/shared_examples/features/search_shared_examples.rb5
-rw-r--r--spec/support/test_env.rb86
-rw-r--r--spec/views/ci/lints/show.html.haml_spec.rb4
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb24
-rw-r--r--spec/views/shared/milestones/_issuable.html.haml.rb19
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb4
121 files changed, 4165 insertions, 2270 deletions
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index dfa06c78d46..5163099cd98 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -45,6 +45,17 @@ describe Boards::IssuesController do
expect(parsed_response.length).to eq 2
expect(development.issues.map(&:relative_position)).not_to include(nil)
end
+
+ it 'avoids N+1 database queries' do
+ create(:labeled_issue, project: project, labels: [development])
+ control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: board, list: list2) }.count
+
+ # 25 issues is bigger than the page size
+ # the relative position will ignore the `#make_sure_position_set` queries
+ create_list(:labeled_issue, 25, project: project, labels: [development], assignees: [johndoe], relative_position: 1)
+
+ expect { list_issues(user: user, board: board, list: list2) }.not_to exceed_query_limit(control_count)
+ end
end
context 'with invalid list id' do
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index a5f544b4f92..a66b4ab0902 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -25,7 +25,8 @@ describe Profiles::PreferencesController do
def go(params: {}, format: :js)
params.reverse_merge!(
color_scheme_id: '1',
- dashboard: 'stars'
+ dashboard: 'stars',
+ theme_id: '1'
)
patch :update, user: params, format: format
@@ -40,7 +41,8 @@ describe Profiles::PreferencesController do
it "changes the user's preferences" do
prefs = {
color_scheme_id: '1',
- dashboard: 'stars'
+ dashboard: 'stars',
+ theme_id: '2'
}.with_indifferent_access
expect(user).to receive(:assign_attributes).with(prefs)
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 745d051a5c1..5e0b57e9b2e 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -367,5 +367,20 @@ describe Projects::BranchesController do
expect(parsed_response.first).to eq 'master'
end
end
+
+ context 'when branch contains an invalid UTF-8 sequence' do
+ before do
+ project.repository.create_branch("wrong-\xE5-utf8-sequence")
+ end
+
+ it 'return with a status 200' do
+ get :index,
+ namespace_id: project.namespace,
+ project_id: project,
+ format: :html
+
+ expect(response).to have_http_status(200)
+ end
+ end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 5d9403c23ac..b4a22a46b51 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -900,5 +900,37 @@ describe Projects::IssuesController do
expect(JSON.parse(response.body).first.keys).to match_array(%w[id reply_id expanded notes individual_note])
end
+
+ context 'with cross-reference system note', :request_store do
+ let(:new_issue) { create(:issue) }
+ let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" }
+
+ before do
+ create(:discussion_note_on_issue, :system, noteable: issue, project: issue.project, note: cross_reference)
+ end
+
+ it 'filters notes that the user should not see' do
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
+
+ expect(JSON.parse(response.body).count).to eq(1)
+ end
+
+ it 'does not result in N+1 queries' do
+ # Instantiate the controller variables to ensure QueryRecorder has an accurate base count
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
+
+ RequestStore.clear!
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
+ end.count
+
+ RequestStore.clear!
+
+ create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference)
+
+ expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count)
+ end
+ end
end
end
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index 2577d98df6f..7ce6a61d50c 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -25,7 +25,7 @@ feature 'Group merge requests page' do
end
it 'ignores archived merge request count badges in navbar' do
- expect( page.find('[aria-label="Merge Requests"] span.badge.count').text).to eq("1")
+ expect(first(:link, text: 'Merge Requests').find('.badge').text).to eq("1")
end
it 'ignores archived merge request count badges in state-filters' do
diff --git a/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb b/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
new file mode 100644
index 00000000000..5ed4f3ad2bc
--- /dev/null
+++ b/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+feature 'Groups > User sees users dropdowns in issuables list' do
+ let(:entity) { create(:group) }
+ let(:user_in_dropdown) { create(:user) }
+ let!(:user_not_in_dropdown) { create(:user) }
+ let!(:project) { create(:project, group: entity) }
+
+ before do
+ entity.add_developer(user_in_dropdown)
+ end
+
+ it_behaves_like 'issuable user dropdown behaviors' do
+ let(:issuable) { create(:issue, project: project) }
+ let(:issuables_path) { issues_group_path(entity) }
+ end
+
+ it_behaves_like 'issuable user dropdown behaviors' do
+ let(:issuable) { create(:merge_request, source_project: project) }
+ let(:issuables_path) { merge_requests_group_path(entity) }
+ end
+end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 9261acda9dc..7437c469a72 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -159,7 +159,7 @@ feature 'Issues > User uses quick actions', js: true do
describe 'move the issue to another project' do
let(:issue) { create(:issue, project: project) }
- context 'when the project is valid', js: true do
+ context 'when the project is valid' do
let(:target_project) { create(:project, :public) }
before do
@@ -180,7 +180,7 @@ feature 'Issues > User uses quick actions', js: true do
end
end
- context 'when the project is valid but the user not authorized', js: true do
+ context 'when the project is valid but the user not authorized' do
let(:project_unauthorized) {create(:project, :public)}
before do
@@ -196,7 +196,7 @@ feature 'Issues > User uses quick actions', js: true do
end
end
- context 'when the project is invalid', js: true do
+ context 'when the project is invalid' do
before do
sign_in(user)
visit project_issue_path(project, issue)
@@ -210,7 +210,7 @@ feature 'Issues > User uses quick actions', js: true do
end
end
- context 'when the user issues multiple commands', js: true do
+ context 'when the user issues multiple commands' do
let(:target_project) { create(:project, :public) }
let(:milestone) { create(:milestone, title: '1.0', project: project) }
let(:target_milestone) { create(:milestone, title: '1.0', project: target_project) }
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index fd991293ee9..443b596b3c6 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -142,6 +142,24 @@ describe 'Merge request', :js do
end
end
+ context 'view merge request where project has CI setup but no CI status' do
+ before do
+ pipeline = create(:ci_pipeline, project: project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch)
+ create(:ci_build, pipeline: pipeline)
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'has pipeline error text' do
+ # Wait for the `ci_status` and `merge_check` requests
+ wait_for_requests
+
+ expect(page).to have_text('Could not connect to the CI server. Please check your settings and try again')
+ end
+ end
+
context 'view merge request with MWPS enabled but automatically merge fails' do
before do
merge_request.update(
diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb
index 624f13922ed..50c5e0bb65f 100644
--- a/spec/features/milestones/show_spec.rb
+++ b/spec/features/milestones/show_spec.rb
@@ -18,9 +18,9 @@ describe 'Milestone show' do
it 'avoids N+1 database queries' do
create(:labeled_issue, issue_params)
- control_count = ActiveRecord::QueryRecorder.new { visit_milestone }.count
+ control = ActiveRecord::QueryRecorder.new { visit_milestone }
create_list(:labeled_issue, 10, issue_params)
- expect { visit_milestone }.not_to exceed_query_limit(control_count)
+ expect { visit_milestone }.not_to exceed_query_limit(control)
end
end
diff --git a/spec/features/profiles/user_visits_profile_account_page_spec.rb b/spec/features/profiles/user_visits_profile_account_page_spec.rb
new file mode 100644
index 00000000000..a8c08a680d7
--- /dev/null
+++ b/spec/features/profiles/user_visits_profile_account_page_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe 'User visits the profile account page' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ visit(profile_account_path)
+ end
+
+ it 'shows correct menu item' do
+ expect(page).to have_active_navigation('Account')
+ end
+end
diff --git a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
new file mode 100644
index 00000000000..a50ebb29e01
--- /dev/null
+++ b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe 'User visits the authentication log' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ visit(audit_log_profile_path)
+ end
+
+ it 'shows correct menu item' do
+ expect(page).to have_active_navigation('Authentication log')
+ end
+end
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index c935cdfd5c4..924ee0e4174 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -1,14 +1,19 @@
require 'spec_helper'
-describe 'Profile > Preferences', :js do
+describe 'User visits the profile preferences page' do
let(:user) { create(:user) }
before do
sign_in(user)
- visit profile_preferences_path
+
+ visit(profile_preferences_path)
+ end
+
+ it 'shows correct menu item' do
+ expect(page).to have_active_navigation('Preferences')
end
- describe 'User changes their syntax highlighting theme' do
+ describe 'User changes their syntax highlighting theme', :js do
it 'creates a flash message' do
choose 'user_color_scheme_id_5'
@@ -27,7 +32,7 @@ describe 'Profile > Preferences', :js do
end
end
- describe 'User changes their default dashboard' do
+ describe 'User changes their default dashboard', :js do
it 'creates a flash message' do
select 'Starred Projects', from: 'user_dashboard'
click_button 'Save'
diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb
new file mode 100644
index 00000000000..6601d3039ed
--- /dev/null
+++ b/spec/features/profiles/user_visits_profile_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe 'User visits their profile' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ visit(profile_path)
+ end
+
+ it 'shows correct menu item' do
+ expect(page).to have_active_navigation('Profile')
+ end
+end
diff --git a/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb
new file mode 100644
index 00000000000..685bf44619d
--- /dev/null
+++ b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe 'User visits the profile SSH keys page' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ visit(profile_keys_path)
+ end
+
+ it 'shows correct menu item' do
+ expect(page).to have_active_navigation('SSH Keys')
+ end
+end
diff --git a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb b/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
new file mode 100644
index 00000000000..adff0a10f0e
--- /dev/null
+++ b/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe 'User interacts with awards in an issue', :js do
+ let(:issue) { create(:issue, project: project)}
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(project_issue_path(project, issue))
+ end
+
+ it 'toggles the thumbsup award emoji' do
+ page.within('.awards') do
+ thumbsup = page.first('.award-control')
+ thumbsup.click
+ thumbsup.hover
+
+ expect(page).to have_selector('.js-emoji-btn')
+ expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+ expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
+
+ thumbsup = page.first('.award-control')
+ thumbsup.click
+ thumbsup.hover
+
+ expect(page).to have_selector('.award-control.js-emoji-btn')
+ expect(page.all('.award-control.js-emoji-btn').size).to eq(2)
+
+ page.all('.award-control.js-emoji-btn').each do |element|
+ expect(element['title']).to eq('')
+ end
+
+ page.all('.award-control .js-counter').each do |element|
+ expect(element).to have_content('0')
+ end
+
+ thumbsup = page.first('.award-control')
+ thumbsup.click
+ thumbsup.hover
+
+ expect(page).to have_selector('.js-emoji-btn')
+ expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+ expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
+ end
+ end
+
+ it 'toggles a custom award emoji' do
+ page.within('.awards') do
+ page.find('.js-add-award').click
+ end
+
+ page.find('.emoji-menu.is-visible')
+
+ expect(page).to have_selector('.js-emoji-menu-search')
+ expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
+
+ page.within('.emoji-menu-content') do
+ emoji_button = page.first('.js-emoji-btn')
+ emoji_button.hover
+ emoji_button.click
+ end
+
+ page.within('.awards') do
+ expect(page).to have_selector('.js-emoji-btn')
+ expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1')
+ expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']")
+
+ expect do
+ page.find('.js-emoji-btn.active').click
+ wait_for_requests
+ end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2)
+ end
+ end
+
+ it 'shows the list of award emoji categories' do
+ page.within('.awards') do
+ page.find('.js-add-award').click
+ end
+
+ page.find('.emoji-menu.is-visible')
+
+ expect(page).to have_selector('.js-emoji-menu-search')
+ expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
+
+ fill_in('emoji-menu-search', with: 'hand')
+
+ page.within('.emoji-menu-content') do
+ expect(page).to have_selector('[data-name="raised_hand"]')
+ end
+ end
+
+ it 'adds an award emoji by a comment' do
+ page.within('.js-main-target-form') do
+ fill_in('note[note]', with: ':smile:')
+
+ click_button('Comment')
+ end
+
+ expect(page).to have_selector('gl-emoji[data-name="smile"]')
+ end
+end
diff --git a/spec/features/projects/commit/user_reverts_commit_spec.rb b/spec/features/projects/commit/user_reverts_commit_spec.rb
new file mode 100644
index 00000000000..221f1d7757e
--- /dev/null
+++ b/spec/features/projects/commit/user_reverts_commit_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe 'User reverts a commit', :js do
+ include RepoHelpers
+
+ let(:project) { create(:project, :repository, namespace: user.namespace) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ visit(project_commit_path(project, sample_commit.id))
+
+ find('.header-action-buttons .dropdown').click
+ find('a[href="#modal-revert-commit"]').click
+ end
+
+ context 'without creating a new merge request' do
+ before do
+ page.within('#modal-revert-commit') do
+ uncheck('create_merge_request')
+ click_button('Revert')
+ end
+ end
+
+ it 'reverts a commit' do
+ expect(page).to have_content('The commit has been successfully reverted.')
+ end
+
+ it 'does not revert a previously reverted commit' do
+ # Visit the comment again once it was reverted.
+ visit project_commit_path(project, sample_commit.id)
+
+ find('.header-action-buttons .dropdown').click
+ find('a[href="#modal-revert-commit"]').click
+
+ page.within('#modal-revert-commit') do
+ uncheck('create_merge_request')
+ click_button('Revert')
+ end
+
+ expect(page).to have_content('Sorry, we cannot revert this commit automatically.')
+ end
+ end
+
+ context 'with creating a new merge request' do
+ it 'reverts a commit' do
+ page.within('#modal-revert-commit') do
+ click_button('Revert')
+ end
+
+ expect(page).to have_content('The commit has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
+ expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master")
+ end
+ end
+end
diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb
index bc102895aaf..a6f52c9ef58 100644
--- a/spec/features/projects/diffs/diff_show_spec.rb
+++ b/spec/features/projects/diffs/diff_show_spec.rb
@@ -108,6 +108,19 @@ feature 'Diff file viewer', :js do
end
end
+ context 'renamed file' do
+ before do
+ visit_commit('6907208d755b60ebeacb2e9dfea74c92c3449a1f')
+ end
+
+ it 'shows the filename with diff highlight' do
+ within('.file-header-content') do
+ expect(page).to have_css('.idiff.left.right.deletion')
+ expect(page).to have_content('files/js/commit.coffee')
+ end
+ end
+ end
+
context 'binary file that appears to be text in the first 1024 bytes' do
before do
# The file we're visiting is smaller than 10 KB and we want it collapsed
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
new file mode 100644
index 00000000000..21c9acc7ac0
--- /dev/null
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe 'User browses a job', :js do
+ let!(:build) { create(:ci_build, :coverage, pipeline: pipeline) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+ let(:project) { create(:project, :repository, namespace: user.namespace) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ project.enable_ci
+ build.success
+ build.trace.set('job trace')
+
+ sign_in(user)
+
+ visit(project_job_path(project, build))
+ end
+
+ it 'erases the job log' do
+ expect(page).to have_content("Job ##{build.id}")
+ expect(page).to have_css('#build-trace')
+
+ click_link('Erase')
+
+ expect(build).not_to have_trace
+ expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_metadata.exists?).to be_falsy
+ expect(page).to have_no_css('.artifacts')
+
+ page.within('.erased') do
+ expect(page).to have_content('Job has been erased')
+ end
+
+ expect(build.project.running_or_pending_build_count).to eq(build.project.builds.running_or_pending.count(:all))
+ end
+end
diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb
new file mode 100644
index 00000000000..767777f3bf9
--- /dev/null
+++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'User browses jobs' do
+ let!(:build) { create(:ci_build, :coverage, pipeline: pipeline) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+ let(:project) { create(:project, :repository, namespace: user.namespace) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ project.enable_ci
+ project.update_attribute(:build_coverage_regex, /Coverage (\d+)%/)
+
+ sign_in(user)
+
+ visit(project_jobs_path(project))
+ end
+
+ it 'shows the coverage' do
+ page.within('td.coverage') do
+ expect(page).to have_content('99.9%')
+ end
+ end
+
+ it 'shows the "CI Lint" button' do
+ page.within('.nav-controls') do
+ ci_lint_tool_link = page.find_link('CI lint')
+
+ expect(ci_lint_tool_link[:href]).to end_with(ci_lint_path)
+ end
+ end
+end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 3b5c6966287..a4ed589f3de 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -164,9 +164,9 @@ feature 'Jobs' do
end
it 'links to issues/new with the title and description filled in' do
- button_title = "Build Failed ##{job.id}"
- job_path = project_job_path(project, job)
- options = { issue: { title: button_title, description: job_path } }
+ button_title = "Job Failed ##{job.id}"
+ job_url = project_job_path(project, job)
+ options = { issue: { title: button_title, description: "Job [##{job.id}](#{job_url}) failed for #{job.sha}:\n" } }
href = new_project_issue_path(project, options)
diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
new file mode 100644
index 00000000000..1bd2098af6d
--- /dev/null
+++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe 'User comments on a snippet', :js do
+ let(:project) { create(:project) }
+ let!(:snippet) { create(:project_snippet, project: project, author: user) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(project_snippet_path(project, snippet))
+ end
+
+ it 'leaves a comment on a snippet' do
+ page.within('.js-main-target-form') do
+ fill_in('note_note', with: 'Good snippet!')
+ click_button('Comment')
+ end
+
+ wait_for_requests
+
+ expect(page).to have_content('Good snippet!')
+ end
+end
diff --git a/spec/features/projects/snippets/user_deletes_snippet_spec.rb b/spec/features/projects/snippets/user_deletes_snippet_spec.rb
new file mode 100644
index 00000000000..ca5f7981c33
--- /dev/null
+++ b/spec/features/projects/snippets/user_deletes_snippet_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe 'User deletes a snippet' do
+ let(:project) { create(:project) }
+ let!(:snippet) { create(:project_snippet, project: project, author: user) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(project_snippet_path(project, snippet))
+ end
+
+ it 'deletes a snippet' do
+ first(:link, 'Delete').click
+
+ expect(page).not_to have_content(snippet.title)
+ end
+end
diff --git a/spec/features/projects/snippets/user_updates_snippet_spec.rb b/spec/features/projects/snippets/user_updates_snippet_spec.rb
new file mode 100644
index 00000000000..09a390443cf
--- /dev/null
+++ b/spec/features/projects/snippets/user_updates_snippet_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe 'User updates a snippet' do
+ let(:project) { create(:project) }
+ let!(:snippet) { create(:project_snippet, project: project, author: user) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(project_snippet_path(project, snippet))
+ end
+
+ it 'updates a snippet' do
+ page.within('.detail-page-header') do
+ first(:link, 'Edit').click
+ end
+
+ fill_in('project_snippet_title', with: 'Snippet new title')
+ click_button('Save')
+
+ expect(page).to have_content('Snippet new title')
+ end
+end
diff --git a/spec/features/projects/snippets/user_views_snippets_spec.rb b/spec/features/projects/snippets/user_views_snippets_spec.rb
new file mode 100644
index 00000000000..e9992e00ca8
--- /dev/null
+++ b/spec/features/projects/snippets/user_views_snippets_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe 'User views snippets' do
+ let(:project) { create(:project) }
+ let!(:project_snippet) { create(:project_snippet, project: project, author: user) }
+ let!(:snippet) { create(:snippet, author: user) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(project_snippets_path(project))
+ end
+
+ it 'shows snippets' do
+ expect(page).to have_content(project_snippet.title)
+ expect(page).not_to have_content(snippet.title)
+ end
+end
diff --git a/spec/features/projects/user_archives_project_spec.rb b/spec/features/projects/user_archives_project_spec.rb
new file mode 100644
index 00000000000..72063d13c2a
--- /dev/null
+++ b/spec/features/projects/user_archives_project_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe 'User archives a project' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+
+ sign_in(user)
+ end
+
+ context 'when a project is archived' do
+ let(:project) { create(:project, :archived, namespace: user.namespace) }
+
+ before do
+ visit(edit_project_path(project))
+ end
+
+ it 'unarchives a project' do
+ expect(page).to have_content('Unarchive project')
+
+ click_link('Unarchive')
+
+ expect(page).not_to have_content('Archived project')
+ end
+ end
+
+ context 'when a project is unarchived' do
+ let(:project) { create(:project, :repository, namespace: user.namespace) }
+
+ before do
+ visit(edit_project_path(project))
+ end
+
+ it 'archives a project' do
+ expect(page).to have_content('Archive project')
+
+ click_link('Archive')
+
+ expect(page).to have_content('Archived')
+ end
+ end
+end
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
new file mode 100644
index 00000000000..0ed797a62ea
--- /dev/null
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe 'User searches for code' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, namespace: user.namespace) }
+
+ context 'when signed in' do
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'finds a file' do
+ visit(project_path(project))
+
+ page.within('.search') do
+ fill_in('search', with: 'application.js')
+ click_button('Go')
+ end
+
+ click_link('Code')
+
+ expect(page).to have_selector('.file-content .code')
+ expect(page).to have_selector("span.line[lang='javascript']")
+ end
+
+ context 'when on a project page', :js do
+ before do
+ visit(search_path)
+ end
+
+ include_examples 'top right search form'
+
+ it 'finds code' do
+ find('.js-search-project-dropdown').trigger('click')
+
+ page.within('.project-filter') do
+ click_link(project.name_with_namespace)
+ end
+
+ fill_in('dashboard_search', with: 'rspec')
+ find('.btn-search').trigger('click')
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
+ end
+ end
+ end
+ end
+
+ context 'when signed out' do
+ let(:project) { create(:project, :public, :repository) }
+
+ before do
+ visit(project_path(project))
+ end
+
+ it 'finds code' do
+ fill_in('search', with: 'rspec')
+ click_button('Go')
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
+ end
+ end
+ end
+end
diff --git a/spec/features/search/user_searches_for_comments_spec.rb b/spec/features/search/user_searches_for_comments_spec.rb
new file mode 100644
index 00000000000..c7c469a262c
--- /dev/null
+++ b/spec/features/search/user_searches_for_comments_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe 'User searches for comments' do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_reporter(user)
+ sign_in(user)
+
+ visit(project_path(project))
+ end
+
+ context 'when a comment is in commits' do
+ context 'when comment belongs to an invalid commit' do
+ let(:comment) { create(:note_on_commit, author: user, project: project, commit_id: 12345678, note: 'Bug here') }
+
+ it 'finds a commit' do
+ page.within('.search') do
+ fill_in('search', with: comment.note)
+ click_button('Go')
+ end
+
+ click_link('Comments')
+
+ expect(page).to have_text('Commit deleted')
+ expect(page).to have_text('12345678')
+ end
+ end
+ end
+
+ context 'when a comment is in a snippet' do
+ let(:snippet) { create(:project_snippet, :private, project: project, author: user, title: 'Some title') }
+ let(:comment) { create(:note, noteable: snippet, author: user, note: 'Supercalifragilisticexpialidocious', project: project) }
+
+ it 'finds a snippet' do
+ page.within('.search') do
+ fill_in('search', with: comment.note)
+ click_button('Go')
+ end
+
+ click_link('Comments')
+
+ expect(page).to have_link(snippet.title)
+ end
+ end
+end
diff --git a/spec/features/search/user_searches_for_commits_spec.rb b/spec/features/search/user_searches_for_commits_spec.rb
new file mode 100644
index 00000000000..28cae444588
--- /dev/null
+++ b/spec/features/search/user_searches_for_commits_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe 'User searches for commits' do
+ let(:project) { create(:project, :repository) }
+ let(:sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_reporter(user)
+ sign_in(user)
+
+ visit(search_path(project_id: project.id))
+ end
+
+ context 'when searching by SHA' do
+ it 'finds a commit and redirects to its page' do
+ fill_in('search', with: sha)
+ click_button('Search')
+
+ expect(page).to have_current_path(project_commit_path(project, sha))
+ end
+
+ it 'finds a commit in uppercase and redirects to its page' do
+ fill_in('search', with: sha.upcase)
+ click_button('Search')
+
+ expect(page).to have_current_path(project_commit_path(project, sha))
+ end
+ end
+
+ context 'when searching by message' do
+ it 'finds a commit and holds on /search page' do
+ create_commit('Message referencing another sha: "deadbeef"', project, user, 'master')
+
+ fill_in('search', with: 'deadbeef')
+ click_button('Search')
+
+ expect(page).to have_current_path('/search', only_path: true)
+ end
+
+ it 'finds multiple commits' do
+ fill_in('search', with: 'See merge request')
+ click_button('Search')
+ click_link('Commits')
+
+ expect(page).to have_selector('.commit-row-description', count: 9)
+ end
+ end
+end
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
new file mode 100644
index 00000000000..630a81b1c5e
--- /dev/null
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -0,0 +1,76 @@
+require 'spec_helper'
+
+describe 'User searches for issues', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let!(:issue1) { create(:issue, title: 'Foo', project: project) }
+ let!(:issue2) { create(:issue, title: 'Bar', project: project) }
+
+ context 'when signed in' do
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(search_path)
+ end
+
+ include_examples 'top right search form'
+
+ it 'finds an issue' do
+ fill_in('dashboard_search', with: issue1.title)
+ find('.btn-search').trigger('click')
+
+ page.within('.search-filter') do
+ click_link('Issues')
+ end
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(issue1.title).and have_no_link(issue2.title)
+ end
+ end
+
+ context 'when on a project page' do
+ it 'finds an issue' do
+ find('.js-search-project-dropdown').trigger('click')
+
+ page.within('.project-filter') do
+ click_link(project.name_with_namespace)
+ end
+
+ fill_in('dashboard_search', with: issue1.title)
+ find('.btn-search').trigger('click')
+
+ page.within('.search-filter') do
+ click_link('Issues')
+ end
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(issue1.title).and have_no_link(issue2.title)
+ end
+ end
+ end
+ end
+
+ context 'when signed out' do
+ let(:project) { create(:project, :public) }
+
+ before do
+ visit(search_path)
+ end
+
+ include_examples 'top right search form'
+
+ it 'finds an issue' do
+ fill_in('dashboard_search', with: issue1.title)
+ find('.btn-search').trigger('click')
+
+ page.within('.search-filter') do
+ click_link('Issues')
+ end
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(issue1.title).and have_no_link(issue2.title)
+ end
+ end
+ end
+end
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
new file mode 100644
index 00000000000..116256682f4
--- /dev/null
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe 'User searches for merge requests', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let!(:merge_request1) { create(:merge_request, title: 'Foo', source_project: project, target_project: project) }
+ let!(:merge_request2) { create(:merge_request, :simple, title: 'Bar', source_project: project, target_project: project) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(search_path)
+ end
+
+ include_examples 'top right search form'
+
+ it 'finds a merge request' do
+ fill_in('dashboard_search', with: merge_request1.title)
+ find('.btn-search').trigger('click')
+
+ page.within('.search-filter') do
+ click_link('Merge requests')
+ end
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(merge_request1.title).and have_no_link(merge_request2.title)
+ end
+ end
+
+ context 'when on a project page' do
+ it 'finds a merge request' do
+ find('.js-search-project-dropdown').trigger('click')
+
+ page.within('.project-filter') do
+ click_link(project.name_with_namespace)
+ end
+
+ fill_in('dashboard_search', with: merge_request1.title)
+ find('.btn-search').trigger('click')
+
+ page.within('.search-filter') do
+ click_link('Merge requests')
+ end
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(merge_request1.title).and have_no_link(merge_request2.title)
+ end
+ end
+ end
+end
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
new file mode 100644
index 00000000000..4fa9fe9ce8c
--- /dev/null
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe 'User searches for milestones', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let!(:milestone1) { create(:milestone, title: 'Foo', project: project) }
+ let!(:milestone2) { create(:milestone, title: 'Bar', project: project) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(search_path)
+ end
+
+ include_examples 'top right search form'
+
+ it 'finds a milestone' do
+ fill_in('dashboard_search', with: milestone1.title)
+ find('.btn-search').trigger('click')
+
+ page.within('.search-filter') do
+ click_link('Milestones')
+ end
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(milestone1.title).and have_no_link(milestone2.title)
+ end
+ end
+
+ context 'when on a project page' do
+ it 'finds a milestone' do
+ find('.js-search-project-dropdown').trigger('click')
+
+ page.within('.project-filter') do
+ click_link(project.name_with_namespace)
+ end
+
+ fill_in('dashboard_search', with: milestone1.title)
+ find('.btn-search').trigger('click')
+
+ page.within('.search-filter') do
+ click_link('Milestones')
+ end
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(milestone1.title).and have_no_link(milestone2.title)
+ end
+ end
+ end
+end
diff --git a/spec/features/search/user_searches_for_projects_spec.rb b/spec/features/search/user_searches_for_projects_spec.rb
new file mode 100644
index 00000000000..242e437e41c
--- /dev/null
+++ b/spec/features/search/user_searches_for_projects_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe 'User searches for projects' do
+ let!(:project) { create(:project, :public, name: 'Shop') }
+
+ context 'when signed out' do
+ include_examples 'top right search form'
+
+ it 'finds a project' do
+ visit(search_path)
+
+ fill_in('dashboard_search', with: project.name[0..3])
+ click_button('Search')
+
+ expect(page).to have_link(project.name)
+ end
+
+ it 'preserves the group being searched in' do
+ visit(search_path(group_id: project.namespace.id))
+
+ fill_in('search', with: 'foo')
+ click_button('Search')
+
+ expect(find('#group_id', visible: false).value).to eq(project.namespace.id.to_s)
+ end
+
+ it 'preserves the project being searched in' do
+ visit(search_path(project_id: project.id))
+
+ fill_in('search', with: 'foo')
+ click_button('Search')
+
+ expect(find('#project_id', visible: false).value).to eq(project.id.to_s)
+ end
+ end
+end
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
new file mode 100644
index 00000000000..1ea56479ecc
--- /dev/null
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'User searches for wiki pages', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let!(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'test_wiki', content: 'Some Wiki content' }) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit(search_path)
+ end
+
+ include_examples 'top right search form'
+
+ it 'finds a page' do
+ find('.js-search-project-dropdown').trigger('click')
+
+ page.within('.project-filter') do
+ click_link(project.name_with_namespace)
+ end
+
+ fill_in('dashboard_search', with: 'content')
+ find('.btn-search').trigger('click')
+
+ page.within('.search-filter') do
+ click_link('Wiki')
+ end
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(wiki_page.title)
+ end
+ end
+end
diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
new file mode 100644
index 00000000000..5ddea36add5
--- /dev/null
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -0,0 +1,90 @@
+require 'spec_helper'
+
+describe 'User uses header search field' do
+ include FilteredSearchHelpers
+
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_reporter(user)
+ sign_in(user)
+
+ visit(project_path(project))
+ end
+
+ it 'starts searching by pressing the enter key', :js do
+ fill_in('search', with: 'gitlab')
+ find('#search').native.send_keys(:enter)
+
+ page.within('.breadcrumbs-sub-title') do
+ expect(page).to have_content('Search')
+ end
+ end
+
+ it 'contains location badge' do
+ expect(page).to have_selector('.has-location-badge')
+ end
+
+ context 'when clicking the search field', :js do
+ before do
+ page.find('#search').click
+ end
+
+ it 'shows category search dropdown' do
+ expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
+ end
+
+ context 'when clicking issues' do
+ let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
+
+ it 'shows assigned issues' do
+ find('.dropdown-menu').click_link('Issues assigned to me')
+
+ expect(page).to have_selector('.filtered-search')
+ expect_tokens([assignee_token(user.name)])
+ expect_filtered_search_input_empty
+ end
+
+ it 'shows created issues' do
+ find('.dropdown-menu').click_link("Issues I've created")
+
+ expect(page).to have_selector('.filtered-search')
+ expect_tokens([author_token(user.name)])
+ expect_filtered_search_input_empty
+ end
+ end
+
+ context 'when clicking merge requests' do
+ let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) }
+
+ it 'shows assigned merge requests' do
+ find('.dropdown-menu').click_link('Merge requests assigned to me')
+
+ expect(page).to have_selector('.merge-requests-holder')
+ expect_tokens([assignee_token(user.name)])
+ expect_filtered_search_input_empty
+ end
+
+ it 'shows created merge requests' do
+ find('.dropdown-menu').click_link("Merge requests I've created")
+
+ expect(page).to have_selector('.merge-requests-holder')
+ expect_tokens([author_token(user.name)])
+ expect_filtered_search_input_empty
+ end
+ end
+ end
+
+ context 'when entering text into the search field', :js do
+ before do
+ page.within('.search-input-wrap') do
+ fill_in('search', with: project.name[0..3])
+ end
+ end
+
+ it 'does not display the category search dropdown' do
+ expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
+ end
+ end
+end
diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb
new file mode 100644
index 00000000000..95f3eb5e805
--- /dev/null
+++ b/spec/features/search/user_uses_search_filters_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe 'User uses search filters', :js do
+ let(:group) { create(:group) }
+ let!(:group_project) { create(:project, group: group) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_reporter(user)
+ group.add_owner(user)
+ sign_in(user)
+
+ visit(search_path)
+ end
+
+ context' when filtering by group' do
+ it 'shows group projects' do
+ find('.js-search-group-dropdown').trigger('click')
+
+ wait_for_requests
+
+ page.within('.search-holder') do
+ click_link(group.name)
+ end
+
+ expect(find('.js-search-group-dropdown')).to have_content(group.name)
+
+ page.within('.project-filter') do
+ find('.js-search-project-dropdown').trigger('click')
+
+ wait_for_requests
+
+ expect(page).to have_link(group_project.name_with_namespace)
+ end
+ end
+ end
+
+ context' when filtering by project' do
+ it 'shows a project' do
+ page.within('.project-filter') do
+ find('.js-search-project-dropdown').trigger('click')
+
+ wait_for_requests
+
+ click_link(project.name_with_namespace)
+ end
+
+ expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace)
+ end
+ end
+end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
deleted file mode 100644
index 8f6d0bb9d1b..00000000000
--- a/spec/features/search_spec.rb
+++ /dev/null
@@ -1,310 +0,0 @@
-require 'spec_helper'
-
-describe "Search" do
- include FilteredSearchHelpers
-
- let(:user) { create(:user) }
- let(:project) { create(:project, namespace: user.namespace) }
- let!(:issue) { create(:issue, project: project, assignees: [user]) }
- let!(:issue2) { create(:issue, project: project, author: user) }
-
- before do
- sign_in(user)
- project.team << [user, :reporter]
- visit search_path
- end
-
- it 'does not show top right search form' do
- expect(page).not_to have_selector('.search')
- end
-
- context 'search filters', js: true do
- let(:group) { create(:group) }
- let!(:group_project) { create(:project, group: group) }
-
- before do
- group.add_owner(user)
- end
-
- it 'shows group name after filtering' do
- find('.js-search-group-dropdown').trigger('click')
- wait_for_requests
-
- page.within '.search-holder' do
- click_link group.name
- end
-
- expect(find('.js-search-group-dropdown')).to have_content(group.name)
- end
-
- it 'filters by group projects after filtering by group' do
- find('.js-search-group-dropdown').trigger('click')
- wait_for_requests
-
- page.within '.search-holder' do
- click_link group.name
- end
-
- expect(find('.js-search-group-dropdown')).to have_content(group.name)
-
- page.within('.project-filter') do
- find('.js-search-project-dropdown').trigger('click')
- wait_for_requests
-
- expect(page).to have_link(group_project.name_with_namespace)
- end
- end
-
- it 'shows project name after filtering' do
- page.within('.project-filter') do
- find('.js-search-project-dropdown').trigger('click')
- wait_for_requests
-
- click_link project.name_with_namespace
- end
-
- expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace)
- end
- end
-
- describe 'searching for Projects' do
- it 'finds a project' do
- page.within '.search-holder' do
- fill_in "search", with: project.name[0..3]
- click_button "Search"
- end
-
- expect(page).to have_content project.name
- end
- end
-
- context 'search for comments' do
- context 'when comment belongs to a invalid commit' do
- let(:project) { create(:project, :repository) }
- let(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'Bug here') }
-
- before do
- note.update_attributes(commit_id: 12345678)
- end
-
- it 'finds comment' do
- visit project_path(project)
-
- page.within '.search' do
- fill_in 'search', with: note.note
- click_button 'Go'
- end
-
- click_link 'Comments'
-
- expect(page).to have_text("Commit deleted")
- expect(page).to have_text("12345678")
- end
- end
-
- it 'finds a snippet' do
- snippet = create(:project_snippet, :private, project: project, author: user, title: 'Some title')
- note = create(:note,
- noteable: snippet,
- author: user,
- note: 'Supercalifragilisticexpialidocious',
- project: project)
- # Must visit project dashboard since global search won't search
- # everything (e.g. comments, snippets, etc.)
- visit project_path(project)
-
- page.within '.search' do
- fill_in 'search', with: note.note
- click_button 'Go'
- end
-
- click_link 'Comments'
-
- expect(page).to have_link(snippet.title)
- end
-
- it 'finds a commit' do
- project = create(:project, :repository) { |p| p.add_reporter(user) }
- visit project_path(project)
-
- page.within '.search' do
- fill_in 'search', with: 'add'
- click_button 'Go'
- end
-
- click_link "Commits"
-
- expect(page).to have_selector('.commit-row-description')
- end
-
- it 'finds a code' do
- project = create(:project, :repository) { |p| p.add_reporter(user) }
- visit project_path(project)
-
- page.within '.search' do
- fill_in 'search', with: 'application.js'
- click_button 'Go'
- end
-
- click_link "Code"
-
- expect(page).to have_selector('.file-content .code')
-
- expect(page).to have_selector("span.line[lang='javascript']")
- end
- end
-
- describe 'Right header search field' do
- it 'allows enter key to search', js: true do
- visit project_path(project)
- fill_in 'search', with: 'gitlab'
- find('#search').native.send_keys(:enter)
-
- page.within '.breadcrumbs-sub-title' do
- expect(page).to have_content 'Search'
- end
- end
-
- describe 'Search in project page' do
- before do
- visit project_path(project)
- end
-
- it 'shows top right search form' do
- expect(page).to have_selector('#search')
- end
-
- it 'contains location badge in top right search form' do
- expect(page).to have_selector('.has-location-badge')
- end
-
- context 'clicking the search field', js: true do
- it 'shows category search dropdown' do
- page.find('#search').click
-
- expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
- end
- end
-
- context 'click the links in the category search dropdown', js: true do
- let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) }
-
- before do
- page.find('#search').click
- end
-
- it 'takes user to her issues page when issues assigned is clicked' do
- find('.dropdown-menu').click_link 'Issues assigned to me'
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'takes user to her issues page when issues authored is clicked' do
- find('.dropdown-menu').click_link "Issues I've created"
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'takes user to her MR page when MR assigned is clicked' do
- find('.dropdown-menu').click_link 'Merge requests assigned to me'
-
- expect(page).to have_selector('.merge-requests-holder')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'takes user to her MR page when MR authored is clicked' do
- find('.dropdown-menu').click_link "Merge requests I've created"
-
- expect(page).to have_selector('.merge-requests-holder')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
- end
-
- context 'entering text into the search field', js: true do
- before do
- page.within '.search-input-wrap' do
- fill_in "search", with: project.name[0..3]
- end
- end
-
- it 'does not display the category search dropdown' do
- expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
- end
- end
- end
- end
-
- describe 'search for commits' do
- let(:project) { create(:project, :repository) }
-
- before do
- visit search_path(project_id: project.id)
- end
-
- it 'redirects to commit page when search by sha and only commit found' do
- fill_in 'search', with: '6d394385cf567f80a8fd85055db1ab4c5295806f'
-
- click_button 'Search'
-
- expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
- end
-
- it 'redirects to single commit regardless of query case' do
- fill_in 'search', with: '6D394385cf'
-
- click_button 'Search'
-
- expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
- end
-
- it 'holds on /search page when the only commit is found by message' do
- create_commit('Message referencing another sha: "deadbeef" ', project, user, 'master')
-
- fill_in 'search', with: 'deadbeef'
- click_button 'Search'
-
- expect(page).to have_current_path('/search', only_path: true)
- end
-
- it 'shows multiple matching commits' do
- fill_in 'search', with: 'See merge request'
-
- click_button 'Search'
- click_link 'Commits'
-
- expect(page).to have_selector('.commit-row-description', count: 9)
- end
- end
-
- context 'anonymous user' do
- let(:project) { create(:project, :public) }
-
- before do
- sign_out(user)
- end
-
- it 'preserves the group being searched in' do
- visit search_path(group_id: project.namespace.id)
-
- fill_in 'search', with: 'foo'
- click_button 'Search'
-
- expect(find('#group_id', visible: false).value).to eq(project.namespace.id.to_s)
- end
-
- it 'preserves the project being searched in' do
- visit search_path(project_id: project.id)
-
- fill_in 'search', with: 'foo'
- click_button 'Search'
-
- expect(find('#project_id', visible: false).value).to eq(project.id.to_s)
- end
- end
-end
diff --git a/spec/finders/autocomplete_users_finder_spec.rb b/spec/finders/autocomplete_users_finder_spec.rb
new file mode 100644
index 00000000000..684af74d750
--- /dev/null
+++ b/spec/finders/autocomplete_users_finder_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+describe AutocompleteUsersFinder do
+ describe '#execute' do
+ let!(:user1) { create(:user, username: 'johndoe') }
+ let!(:user2) { create(:user, :blocked, username: 'notsorandom') }
+ let!(:external_user) { create(:user, :external) }
+ let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
+ let(:current_user) { create(:user) }
+ let(:params) { {} }
+
+ let(:project) { nil }
+ let(:group) { nil }
+
+ subject { described_class.new(params: params, current_user: current_user, project: project, group: group).execute.to_a }
+
+ context 'when current_user not passed or nil' do
+ let(:current_user) { nil }
+
+ it { is_expected.to match_array([]) }
+ end
+
+ context 'when project passed' do
+ let(:project) { create(:project) }
+
+ it { is_expected.to match_array([project.owner]) }
+
+ context 'when author_id passed' do
+ let(:params) { { author_id: user2.id } }
+
+ it { is_expected.to match_array([project.owner, user2]) }
+ end
+ end
+
+ context 'when group passed and project not passed' do
+ let(:group) { create(:group, :public) }
+
+ before do
+ group.add_users([user1], GroupMember::DEVELOPER)
+ end
+
+ it { is_expected.to match_array([user1]) }
+ end
+
+ it { is_expected.to match_array([user1, external_user, omniauth_user, current_user]) }
+
+ context 'when filtered by search' do
+ let(:params) { { search: 'johndoe' } }
+
+ it { is_expected.to match_array([user1]) }
+ end
+
+ context 'when filtered by skip_users' do
+ let(:params) { { skip_users: [omniauth_user.id, current_user.id] } }
+
+ it { is_expected.to match_array([user1, external_user]) }
+ end
+
+ context 'when todos exist' do
+ let!(:pending_todo1) { create(:todo, user: current_user, author: user1, state: :pending) }
+ let!(:pending_todo2) { create(:todo, user: external_user, author: omniauth_user, state: :pending) }
+ let!(:done_todo1) { create(:todo, user: current_user, author: external_user, state: :done) }
+ let!(:done_todo2) { create(:todo, user: user1, author: external_user, state: :done) }
+
+ context 'when filtered by todo_filter without todo_state_filter' do
+ let(:params) { { todo_filter: true } }
+
+ it { is_expected.to match_array([]) }
+ end
+
+ context 'when filtered by todo_filter with pending todo_state_filter' do
+ let(:params) { { todo_filter: true, todo_state_filter: 'pending' } }
+
+ it { is_expected.to match_array([user1]) }
+ end
+
+ context 'when filtered by todo_filter with done todo_state_filter' do
+ let(:params) { { todo_filter: true, todo_state_filter: 'done' } }
+
+ it { is_expected.to match_array([external_user]) }
+ end
+ end
+
+ context 'when filtered by current_user' do
+ let(:current_user) { user2 }
+ let(:params) { { current_user: true } }
+
+ it { is_expected.to match_array([user2, user1, external_user, omniauth_user]) }
+ end
+
+ context 'when filtered by author_id' do
+ let(:params) { { author_id: user2.id } }
+
+ it { is_expected.to match_array([user2, user1, external_user, omniauth_user, current_user]) }
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/public_api/v4/issues.json b/spec/fixtures/api/schemas/public_api/v4/issues.json
index 8acd9488215..03c422ab023 100644
--- a/spec/fixtures/api/schemas/public_api/v4/issues.json
+++ b/spec/fixtures/api/schemas/public_api/v4/issues.json
@@ -9,6 +9,7 @@
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
+ "closed_at": { "type": "date" },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"labels": {
diff --git a/spec/fixtures/api/schemas/public_api/v4/user/login.json b/spec/fixtures/api/schemas/public_api/v4/user/login.json
index 6181b3ccc86..e6c1d9c9d84 100644
--- a/spec/fixtures/api/schemas/public_api/v4/user/login.json
+++ b/spec/fixtures/api/schemas/public_api/v4/user/login.json
@@ -19,6 +19,7 @@
"organization",
"last_sign_in_at",
"confirmed_at",
+ "theme_id",
"color_scheme_id",
"projects_limit",
"current_sign_in_at",
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index b6d892548ef..5e272af6073 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -10,6 +10,8 @@ describe AutoDevopsHelper do
before do
allow(helper).to receive(:can?).with(user, :admin_pipeline, project) { allowed }
allow(helper).to receive(:current_user) { user }
+
+ Feature.get(:auto_devops_banner_disabled).disable
end
subject { helper.show_auto_devops_callout?(project) }
@@ -18,6 +20,14 @@ describe AutoDevopsHelper do
it { is_expected.to eq(true) }
end
+ context 'when the banner is disabled by feature flag' do
+ it 'allows the feature flag to disable' do
+ Feature.get(:auto_devops_banner_disabled).enable
+
+ expect(subject).to be(false)
+ end
+ end
+
context 'when dismissed' do
before do
helper.request.cookies[:auto_devops_settings_dismissed] = 'true'
@@ -55,5 +65,21 @@ describe AutoDevopsHelper do
it { is_expected.to eq(false) }
end
+
+ context 'when master contains a .gitlab-ci.yml file' do
+ before do
+ allow(project.repository).to receive(:gitlab_ci_yml).and_return("script: ['test']")
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when another service is enabled' do
+ before do
+ create(:service, project: project, category: :ci, active: true)
+ end
+
+ it { is_expected.to eq(false) }
+ end
end
end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 0deea0ff6a3..f9c31ac61d8 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -136,9 +136,9 @@ describe DiffHelper do
marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
expect(marked_old_line).to eq(%q{abc <span class="idiff left right deletion">'def'</span>})
- expect(marked_old_line).not_to be_html_safe
+ expect(marked_old_line).to be_html_safe
expect(marked_new_line).to eq(%q{abc <span class="idiff left right addition">"def"</span>})
- expect(marked_new_line).not_to be_html_safe
+ expect(marked_new_line).to be_html_safe
end
end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index a04c87b08eb..8b8080563d3 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe PreferencesHelper do
- describe 'dashboard_choices' do
+ describe '#dashboard_choices' do
it 'raises an exception when defined choices may be missing' do
expect(User).to receive(:dashboards).and_return(foo: 'foo')
expect { helper.dashboard_choices }.to raise_error(RuntimeError)
@@ -26,7 +26,33 @@ describe PreferencesHelper do
end
end
- describe 'user_color_scheme' do
+ describe '#user_application_theme' do
+ context 'with a user' do
+ it "returns user's theme's css_class" do
+ stub_user(theme_id: 3)
+
+ expect(helper.user_application_theme).to eq 'ui_light'
+ end
+
+ it 'returns the default when id is invalid' do
+ stub_user(theme_id: Gitlab::Themes.count + 5)
+
+ allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(1)
+
+ expect(helper.user_application_theme).to eq 'ui_indigo'
+ end
+ end
+
+ context 'without a user' do
+ it 'returns the default theme' do
+ stub_user
+
+ expect(helper.user_application_theme).to eq Gitlab::Themes.default.css_class
+ end
+ end
+ end
+
+ describe '#user_color_scheme' do
context 'with a user' do
it "returns user's scheme's css_class" do
allow(helper).to receive(:current_user)
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 1437479831e..a76c75e0c08 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -469,4 +469,15 @@ describe ProjectsHelper do
expect(recorder.count).to eq(1)
end
end
+
+ describe '#git_user_name' do
+ let(:user) { double(:user, name: 'John "A" Doe53') }
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ it 'parses quotes in name' do
+ expect(helper.send(:git_user_name)).to eq('John \"A\" Doe53')
+ end
+ end
end
diff --git a/spec/initializers/doorkeeper_spec.rb b/spec/initializers/doorkeeper_spec.rb
index 74bdbb01166..1a78196e33d 100644
--- a/spec/initializers/doorkeeper_spec.rb
+++ b/spec/initializers/doorkeeper_spec.rb
@@ -9,8 +9,8 @@ describe Doorkeeper.configuration do
end
describe '#optional_scopes' do
- it 'matches Gitlab::Auth::OPTIONAL_SCOPES' do
- expect(subject.optional_scopes).to eq Gitlab::Auth::OPTIONAL_SCOPES
+ it 'matches Gitlab::Auth.optional_scopes' do
+ expect(subject.optional_scopes).to eq Gitlab::Auth.optional_scopes - Gitlab::Auth::REGISTRY_SCOPES
end
end
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index b3c9bca64cc..02415485d19 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -10,6 +10,7 @@ describe('Dropdown User', () => {
beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'getGroupId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
dropdownUser = new gl.DropdownUser({
@@ -38,6 +39,7 @@ describe('Dropdown User', () => {
beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'getGroupId').and.callFake(() => {});
});
it('should return endpoint', () => {
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index f8b37c0edde..f4b4d7980a4 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -271,12 +271,19 @@ describe('Fly out sidebar navigation', () => {
});
it('sets transform of sub-items', () => {
+ const sidebar = document.createElement('div');
const subItems = el.querySelector('.sidebar-sub-level-items');
+
+ sidebar.style.width = '200px';
+
+ document.body.appendChild(sidebar);
+
+ setSidebar(sidebar);
showSubLevelItems(el);
expect(
subItems.style.transform,
- ).toBe(`translate3d(0px, ${Math.floor(el.getBoundingClientRect().top) - getHeaderHeight()}px, 0px)`);
+ ).toBe(`translate3d(200px, ${Math.floor(el.getBoundingClientRect().top) - getHeaderHeight()}px, 0px)`);
});
it('sets is-above when element is above', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
index c763487d12f..690665ae12c 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -37,6 +37,26 @@ describe('MRWidgetPipeline', () => {
});
});
+ describe('hasPipeline', () => {
+ it('should return true when there is a pipeline', () => {
+ expect(Object.keys(mockData.pipeline).length).toBeGreaterThan(0);
+
+ const vm = createComponent({
+ pipeline: mockData.pipeline,
+ });
+
+ expect(vm.hasPipeline).toBeTruthy();
+ });
+
+ it('should return false when there is no pipeline', () => {
+ const vm = createComponent({
+ pipeline: null,
+ });
+
+ expect(vm.hasPipeline).toBeFalsy();
+ });
+ });
+
describe('hasCIError', () => {
it('should return false when there is no CI error', () => {
const vm = createComponent({
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index c607c9746a4..8f69458ffb0 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -11,6 +11,7 @@ const createComponent = (customConfig = {}) => {
isPipelineActive: false,
pipeline: null,
isPipelineFailed: false,
+ isPipelinePassing: false,
onlyAllowMergeIfPipelineSucceeds: false,
hasCI: false,
ciStatus: null,
@@ -68,6 +69,18 @@ describe('MRWidgetReadyToMerge', () => {
});
describe('computed', () => {
+ describe('shouldShowMergeWhenPipelineSucceedsText', () => {
+ it('should return true with active pipeline', () => {
+ vm.mr.isPipelineActive = true;
+ expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeTruthy();
+ });
+
+ it('should return false with inactive pipeline', () => {
+ vm.mr.isPipelineActive = false;
+ expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeFalsy();
+ });
+ });
+
describe('commitMessageLinkTitle', () => {
const withDesc = 'Include description in commit message';
const withoutDesc = "Don't include description in commit message";
@@ -203,20 +216,55 @@ describe('MRWidgetReadyToMerge', () => {
describe('methods', () => {
describe('isMergeAllowed', () => {
- it('should return false with initial data', () => {
+ it('should return true when no pipeline and not required to succeed', () => {
+ vm.mr.onlyAllowMergeIfPipelineSucceeds = false;
+ vm.mr.isPipelinePassing = false;
expect(vm.isMergeAllowed()).toBeTruthy();
});
- it('should return false when MR is set only merge when pipeline succeeds', () => {
- vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
+ it('should return true when pipeline failed and not required to succeed', () => {
+ vm.mr.onlyAllowMergeIfPipelineSucceeds = false;
+ vm.mr.isPipelinePassing = false;
expect(vm.isMergeAllowed()).toBeTruthy();
});
- it('should return true true', () => {
+ it('should return false when pipeline failed and required to succeed', () => {
vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
- vm.mr.isPipelineFailed = true;
+ vm.mr.isPipelinePassing = false;
expect(vm.isMergeAllowed()).toBeFalsy();
});
+
+ it('should return true when pipeline succeeded and required to succeed', () => {
+ vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
+ vm.mr.isPipelinePassing = true;
+ expect(vm.isMergeAllowed()).toBeTruthy();
+ });
+ });
+
+ describe('shouldShowMergeControls', () => {
+ it('should return false when an external pipeline is running and required to succeed', () => {
+ spyOn(vm, 'isMergeAllowed').and.returnValue(false);
+ vm.mr.isPipelineActive = false;
+ expect(vm.shouldShowMergeControls()).toBeFalsy();
+ });
+
+ it('should return true when the build succeeded or build not required to succeed', () => {
+ spyOn(vm, 'isMergeAllowed').and.returnValue(true);
+ vm.mr.isPipelineActive = false;
+ expect(vm.shouldShowMergeControls()).toBeTruthy();
+ });
+
+ it('should return true when showing the MWPS button and a pipeline is running that needs to be successful', () => {
+ spyOn(vm, 'isMergeAllowed').and.returnValue(false);
+ vm.mr.isPipelineActive = true;
+ expect(vm.shouldShowMergeControls()).toBeTruthy();
+ });
+
+ it('should return true when showing the MWPS button but not required for the pipeline to succeed', () => {
+ spyOn(vm, 'isMergeAllowed').and.returnValue(true);
+ vm.mr.isPipelineActive = true;
+ expect(vm.shouldShowMergeControls()).toBeTruthy();
+ });
});
describe('updateCommitMessage', () => {
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index 669ee248bf1..da66c7504cb 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -59,23 +59,15 @@ describe('mrWidgetOptions', () => {
});
describe('shouldRenderPipelines', () => {
- it('should return true for the initial data', () => {
- expect(vm.shouldRenderPipelines).toBeTruthy();
- });
+ it('should return true when hasCI is true', () => {
+ vm.mr.hasCI = true;
- it('should return true when pipeline is empty but MR.hasCI is set to true', () => {
- vm.mr.pipeline = {};
expect(vm.shouldRenderPipelines).toBeTruthy();
});
- it('should return true when pipeline available', () => {
+ it('should return false when hasCI is false', () => {
vm.mr.hasCI = false;
- expect(vm.shouldRenderPipelines).toBeTruthy();
- });
- it('should return false when there is no pipeline', () => {
- vm.mr.pipeline = {};
- vm.mr.hasCI = false;
expect(vm.shouldRenderPipelines).toBeFalsy();
});
});
diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
index 56dd0198ae2..8e5614b20f0 100644
--- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
+++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
@@ -18,5 +18,39 @@ describe('MergeRequestStore', () => {
store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress });
expect(store.hasSHAChanged).toBe(false);
});
+
+ describe('isPipelinePassing', () => {
+ it('is true when the CI status is `success`', () => {
+ store.setData({ ...mockData, ci_status: 'success' });
+ expect(store.isPipelinePassing).toBe(true);
+ });
+
+ it('is true when the CI status is `success_with_warnings`', () => {
+ store.setData({ ...mockData, ci_status: 'success_with_warnings' });
+ expect(store.isPipelinePassing).toBe(true);
+ });
+
+ it('is false when the CI status is `failed`', () => {
+ store.setData({ ...mockData, ci_status: 'failed' });
+ expect(store.isPipelinePassing).toBe(false);
+ });
+
+ it('is false when the CI status is anything except `success`', () => {
+ store.setData({ ...mockData, ci_status: 'foobarbaz' });
+ expect(store.isPipelinePassing).toBe(false);
+ });
+ });
+
+ describe('isPipelineSkipped', () => {
+ it('should set isPipelineSkipped=true when the CI status is `skipped`', () => {
+ store.setData({ ...mockData, ci_status: 'skipped' });
+ expect(store.isPipelineSkipped).toBe(true);
+ });
+
+ it('should set isPipelineSkipped=false when the CI status is anything except `skipped`', () => {
+ store.setData({ ...mockData, ci_status: 'foobarbaz' });
+ expect(store.isPipelineSkipped).toBe(false);
+ });
+ });
});
});
diff --git a/spec/lib/banzai/pipeline/email_pipeline_spec.rb b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
new file mode 100644
index 00000000000..6a11ca2f9d5
--- /dev/null
+++ b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+describe Banzai::Pipeline::EmailPipeline do
+ describe '.filters' do
+ it 'returns the expected type' do
+ expect(described_class.filters).to be_kind_of(Banzai::FilterArray)
+ end
+
+ it 'excludes ImageLazyLoadFilter' do
+ expect(described_class.filters).not_to be_empty
+ expect(described_class.filters).not_to include(Banzai::Filter::ImageLazyLoadFilter)
+ 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
deleted file mode 100644
index 1efd3113a43..00000000000
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ /dev/null
@@ -1,1697 +0,0 @@
-require 'spec_helper'
-
-module Ci
- describe GitlabCiYamlProcessor, :lib do
- subject { described_class.new(config, path) }
- let(:path) { 'path' }
-
- describe 'our current .gitlab-ci.yml' do
- let(:config) { File.read("#{Rails.root}/.gitlab-ci.yml") }
-
- it 'is valid' do
- error_message = described_class.validation_message(config)
-
- expect(error_message).to be_nil
- end
- end
-
- describe '#build_attributes' do
- subject { described_class.new(config, path).build_attributes(:rspec) }
-
- describe 'coverage entry' do
- describe 'code coverage regexp' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec',
- coverage: '/Code coverage: \d+\.\d+/' })
- end
-
- it 'includes coverage regexp in build attributes' do
- expect(subject)
- .to include(coverage_regex: 'Code coverage: \d+\.\d+')
- end
- end
- end
-
- describe 'retry entry' do
- context 'when retry count is specified' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec', retry: 1 })
- end
-
- it 'includes retry count in build options attribute' do
- expect(subject[:options]).to include(retry: 1)
- end
- end
-
- context 'when retry count is not specified' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec' })
- end
-
- it 'does not persist retry count in the database' do
- expect(subject[:options]).not_to have_key(:retry)
- end
- end
- end
-
- describe 'allow failure entry' do
- context 'when job is a manual action' do
- context 'when allow_failure is defined' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec',
- when: 'manual',
- allow_failure: false })
- end
-
- it 'is not allowed to fail' do
- expect(subject[:allow_failure]).to be false
- end
- end
-
- context 'when allow_failure is not defined' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec',
- when: 'manual' })
- end
-
- it 'is allowed to fail' do
- expect(subject[:allow_failure]).to be true
- end
- end
- end
-
- context 'when job is not a manual action' do
- context 'when allow_failure is defined' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec',
- allow_failure: false })
- end
-
- it 'is not allowed to fail' do
- expect(subject[:allow_failure]).to be false
- end
- end
-
- context 'when allow_failure is not defined' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec' })
- end
-
- it 'is not allowed to fail' do
- expect(subject[:allow_failure]).to be false
- end
- end
- end
- end
- end
-
- describe '#stage_seeds' do
- context 'when no refs policy is specified' do
- let(:config) do
- YAML.dump(production: { stage: 'deploy', script: 'cap prod' },
- rspec: { stage: 'test', script: 'rspec' },
- spinach: { stage: 'test', script: 'spinach' })
- end
-
- let(:pipeline) { create(:ci_empty_pipeline) }
-
- it 'correctly fabricates a stage seeds object' do
- seeds = subject.stage_seeds(pipeline)
-
- expect(seeds.size).to eq 2
- expect(seeds.first.stage[:name]).to eq 'test'
- expect(seeds.second.stage[:name]).to eq 'deploy'
- expect(seeds.first.builds.dig(0, :name)).to eq 'rspec'
- expect(seeds.first.builds.dig(1, :name)).to eq 'spinach'
- expect(seeds.second.builds.dig(0, :name)).to eq 'production'
- end
- end
-
- context 'when refs policy is specified' do
- let(:config) do
- YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
- spinach: { stage: 'test', script: 'spinach', only: ['tags'] })
- end
-
- let(:pipeline) do
- create(:ci_empty_pipeline, ref: 'feature', tag: true)
- end
-
- it 'returns stage seeds only assigned to master to master' do
- seeds = subject.stage_seeds(pipeline)
-
- expect(seeds.size).to eq 1
- expect(seeds.first.stage[:name]).to eq 'test'
- expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
- end
- end
-
- context 'when source policy is specified' do
- let(:config) do
- YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
- spinach: { stage: 'test', script: 'spinach', only: ['schedules'] })
- end
-
- let(:pipeline) do
- create(:ci_empty_pipeline, source: :schedule)
- end
-
- it 'returns stage seeds only assigned to schedules' do
- seeds = subject.stage_seeds(pipeline)
-
- expect(seeds.size).to eq 1
- expect(seeds.first.stage[:name]).to eq 'test'
- expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
- end
- end
-
- context 'when kubernetes policy is specified' do
- let(:pipeline) { create(:ci_empty_pipeline) }
-
- let(:config) do
- YAML.dump(
- spinach: { stage: 'test', script: 'spinach' },
- production: {
- stage: 'deploy',
- script: 'cap',
- only: { kubernetes: 'active' }
- }
- )
- end
-
- context 'when kubernetes is active' do
- let(:project) { create(:kubernetes_project) }
- let(:pipeline) { create(:ci_empty_pipeline, project: project) }
-
- it 'returns seeds for kubernetes dependent job' do
- seeds = subject.stage_seeds(pipeline)
-
- expect(seeds.size).to eq 2
- expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
- expect(seeds.second.builds.dig(0, :name)).to eq 'production'
- end
- end
-
- context 'when kubernetes is not active' do
- it 'does not return seeds for kubernetes dependent job' do
- seeds = subject.stage_seeds(pipeline)
-
- expect(seeds.size).to eq 1
- expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
- end
- end
- end
- end
-
- describe "#builds_for_stage_and_ref" do
- let(:type) { 'test' }
-
- it "returns builds if no branch specified" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec" }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({
- stage: "test",
- stage_idx: 1,
- name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
- options: {
- before_script: ["pwd"],
- script: ["rspec"]
- },
- allow_failure: false,
- when: "on_success",
- environment: nil,
- yaml_variables: []
- })
- end
-
- describe 'only' do
- it "does not return builds if only has another branch" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", only: ["deploy"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
- end
-
- it "does not return builds if only has regexp with another branch" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", only: ["/^deploy$/"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
- end
-
- it "returns builds if only has specified this branch" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", only: ["master"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
- end
-
- it "returns builds if only has a list of branches including specified" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: %w(master deploy) }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
- end
-
- it "returns builds if only has a branches keyword specified" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: ["branches"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
- end
-
- it "does not return builds if only has a tags keyword" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: ["tags"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
- end
-
- it "returns builds if only has special keywords specified and source matches" do
- possibilities = [{ keyword: 'pushes', source: 'push' },
- { keyword: 'web', source: 'web' },
- { keyword: 'triggers', source: 'trigger' },
- { keyword: 'schedules', source: 'schedule' },
- { keyword: 'api', source: 'api' },
- { keyword: 'external', source: 'external' }]
-
- possibilities.each do |possibility|
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(1)
- end
- end
-
- it "does not return builds if only has special keywords specified and source doesn't match" do
- possibilities = [{ keyword: 'pushes', source: 'web' },
- { keyword: 'web', source: 'push' },
- { keyword: 'triggers', source: 'schedule' },
- { keyword: 'schedules', source: 'external' },
- { keyword: 'api', source: 'trigger' },
- { keyword: 'external', source: 'api' }]
-
- possibilities.each do |possibility|
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(0)
- end
- end
-
- it "returns builds if only has current repository path" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: ["branches@path"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
- end
-
- it "does not return builds if only has different repository path" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: ["branches@fork"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
- end
-
- it "returns build only for specified type" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: "test", only: %w(master deploy) },
- staging: { script: "deploy", type: "deploy", only: %w(master deploy) },
- production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, 'fork')
-
- expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2)
- expect(config_processor.builds_for_stage_and_ref("test", "deploy").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(1)
- end
-
- context 'for invalid value' do
- let(:config) { { rspec: { script: "rspec", type: "test", only: only } } }
- let(:processor) { GitlabCiYamlProcessor.new(YAML.dump(config)) }
-
- context 'when it is integer' do
- let(:only) { 1 }
-
- it do
- expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError,
- 'jobs:rspec:only has to be either an array of conditions or a hash')
- end
- end
-
- context 'when it is an array of integers' do
- let(:only) { [1, 1] }
-
- it do
- expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError,
- 'jobs:rspec:only config should be an array of strings or regexps')
- end
- end
-
- context 'when it is invalid regex' do
- let(:only) { ["/*invalid/"] }
-
- it do
- expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError,
- 'jobs:rspec:only config should be an array of strings or regexps')
- end
- end
- end
- end
-
- describe 'except' do
- it "returns builds if except has another branch" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", except: ["deploy"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
- end
-
- it "returns builds if except has regexp with another branch" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", except: ["/^deploy$/"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
- end
-
- it "does not return builds if except has specified this branch" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", except: ["master"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
- end
-
- it "does not return builds if except has a list of branches including specified" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: %w(master deploy) }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
- end
-
- it "does not return builds if except has a branches keyword specified" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: ["branches"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
- end
-
- it "returns builds if except has a tags keyword" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: ["tags"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
- end
-
- it "does not return builds if except has special keywords specified and source matches" do
- possibilities = [{ keyword: 'pushes', source: 'push' },
- { keyword: 'web', source: 'web' },
- { keyword: 'triggers', source: 'trigger' },
- { keyword: 'schedules', source: 'schedule' },
- { keyword: 'api', source: 'api' },
- { keyword: 'external', source: 'external' }]
-
- possibilities.each do |possibility|
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(0)
- end
- end
-
- it "returns builds if except has special keywords specified and source doesn't match" do
- possibilities = [{ keyword: 'pushes', source: 'web' },
- { keyword: 'web', source: 'push' },
- { keyword: 'triggers', source: 'schedule' },
- { keyword: 'schedules', source: 'external' },
- { keyword: 'api', source: 'trigger' },
- { keyword: 'external', source: 'api' }]
-
- possibilities.each do |possibility|
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(1)
- end
- end
-
- it "does not return builds if except has current repository path" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: ["branches@path"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
- end
-
- it "returns builds if except has different repository path" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: ["branches@fork"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
- end
-
- it "returns build except specified type" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@fork"] },
- staging: { script: "deploy", type: "deploy", except: ["master"] },
- production: { script: "deploy", type: "deploy", except: ["master@fork"] }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, 'fork')
-
- expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2)
- expect(config_processor.builds_for_stage_and_ref("test", "test").size).to eq(0)
- expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(0)
- end
-
- context 'for invalid value' do
- let(:config) { { rspec: { script: "rspec", except: except } } }
- let(:processor) { GitlabCiYamlProcessor.new(YAML.dump(config)) }
-
- context 'when it is integer' do
- let(:except) { 1 }
-
- it do
- expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError,
- 'jobs:rspec:except has to be either an array of conditions or a hash')
- end
- end
-
- context 'when it is an array of integers' do
- let(:except) { [1, 1] }
-
- it do
- expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError,
- 'jobs:rspec:except config should be an array of strings or regexps')
- end
- end
-
- context 'when it is invalid regex' do
- let(:except) { ["/*invalid/"] }
-
- it do
- expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError,
- 'jobs:rspec:except config should be an array of strings or regexps')
- end
- end
- end
- end
- end
-
- describe "Scripts handling" do
- let(:config_data) { YAML.dump(config) }
- let(:config_processor) { GitlabCiYamlProcessor.new(config_data, path) }
-
- subject { config_processor.builds_for_stage_and_ref("test", "master").first }
-
- describe "before_script" do
- context "in global context" do
- let(:config) do
- {
- before_script: ["global script"],
- test: { script: ["script"] }
- }
- end
-
- it "return commands with scripts concencaced" do
- expect(subject[:commands]).to eq("global script\nscript")
- end
- end
-
- context "overwritten in local context" do
- let(:config) do
- {
- before_script: ["global script"],
- test: { before_script: ["local script"], script: ["script"] }
- }
- end
-
- it "return commands with scripts concencaced" do
- expect(subject[:commands]).to eq("local script\nscript")
- end
- end
- end
-
- describe "script" do
- let(:config) do
- {
- test: { script: ["script"] }
- }
- end
-
- it "return commands with scripts concencaced" do
- expect(subject[:commands]).to eq("script")
- end
- end
-
- describe "after_script" do
- context "in global context" do
- let(:config) do
- {
- after_script: ["after_script"],
- test: { script: ["script"] }
- }
- end
-
- it "return after_script in options" do
- expect(subject[:options][:after_script]).to eq(["after_script"])
- end
- end
-
- context "overwritten in local context" do
- let(:config) do
- {
- after_script: ["local after_script"],
- test: { after_script: ["local after_script"], script: ["script"] }
- }
- end
-
- it "return after_script in options" do
- expect(subject[:options][:after_script]).to eq(["local after_script"])
- end
- end
- end
- end
-
- describe "Image and service handling" do
- context "when extended docker configuration is used" do
- it "returns image and service when defined" do
- config = YAML.dump({ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
- services: ["mysql", { name: "docker:dind", alias: "docker",
- entrypoint: ["/usr/local/bin/init", "run"],
- command: ["/usr/local/bin/init", "run"] }],
- before_script: ["pwd"],
- rspec: { script: "rspec" } })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- stage: "test",
- stage_idx: 1,
- name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
- options: {
- before_script: ["pwd"],
- script: ["rspec"],
- image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
- services: [{ name: "mysql" },
- { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"],
- command: ["/usr/local/bin/init", "run"] }]
- },
- allow_failure: false,
- when: "on_success",
- environment: nil,
- yaml_variables: []
- })
- end
-
- it "returns image and service when overridden for job" do
- config = YAML.dump({ image: "ruby:2.1",
- services: ["mysql"],
- before_script: ["pwd"],
- rspec: { image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
- services: [{ name: "postgresql", alias: "db-pg",
- entrypoint: ["/usr/local/bin/init", "run"],
- command: ["/usr/local/bin/init", "run"] }, "docker:dind"],
- script: "rspec" } })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- stage: "test",
- stage_idx: 1,
- name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
- options: {
- before_script: ["pwd"],
- script: ["rspec"],
- image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
- services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
- command: ["/usr/local/bin/init", "run"] },
- { name: "docker:dind" }]
- },
- allow_failure: false,
- when: "on_success",
- environment: nil,
- yaml_variables: []
- })
- end
- end
-
- context "when etended docker configuration is not used" do
- it "returns image and service when defined" do
- config = YAML.dump({ image: "ruby:2.1",
- services: ["mysql", "docker:dind"],
- before_script: ["pwd"],
- rspec: { script: "rspec" } })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- stage: "test",
- stage_idx: 1,
- name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
- options: {
- before_script: ["pwd"],
- script: ["rspec"],
- image: { name: "ruby:2.1" },
- services: [{ name: "mysql" }, { name: "docker:dind" }]
- },
- allow_failure: false,
- when: "on_success",
- environment: nil,
- yaml_variables: []
- })
- end
-
- it "returns image and service when overridden for job" do
- config = YAML.dump({ image: "ruby:2.1",
- services: ["mysql"],
- before_script: ["pwd"],
- rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- stage: "test",
- stage_idx: 1,
- name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
- options: {
- before_script: ["pwd"],
- script: ["rspec"],
- image: { name: "ruby:2.5" },
- services: [{ name: "postgresql" }, { name: "docker:dind" }]
- },
- allow_failure: false,
- when: "on_success",
- environment: nil,
- yaml_variables: []
- })
- end
- end
- end
-
- describe 'Variables' do
- let(:config_processor) { GitlabCiYamlProcessor.new(YAML.dump(config), path) }
-
- subject { config_processor.builds.first[:yaml_variables] }
-
- context 'when global variables are defined' do
- let(:variables) do
- { 'VAR1' => 'value1', 'VAR2' => 'value2' }
- end
- let(:config) do
- {
- variables: variables,
- before_script: ['pwd'],
- rspec: { script: 'rspec' }
- }
- end
-
- it 'returns global variables' do
- expect(subject).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
- end
- end
-
- context 'when job and global variables are defined' do
- let(:global_variables) do
- { 'VAR1' => 'global1', 'VAR3' => 'global3' }
- end
- let(:job_variables) do
- { 'VAR1' => 'value1', 'VAR2' => 'value2' }
- end
- let(:config) do
- {
- before_script: ['pwd'],
- variables: global_variables,
- rspec: { script: 'rspec', variables: job_variables }
- }
- end
-
- it 'returns all unique variables' do
- expect(subject).to contain_exactly(
- { key: 'VAR3', value: 'global3', public: true },
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
- end
- end
-
- context 'when job variables are defined' do
- let(:config) do
- {
- before_script: ['pwd'],
- rspec: { script: 'rspec', variables: variables }
- }
- end
-
- context 'when syntax is correct' do
- let(:variables) do
- { 'VAR1' => 'value1', 'VAR2' => 'value2' }
- end
-
- it 'returns job variables' do
- expect(subject).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
- end
- end
-
- context 'when syntax is incorrect' do
- context 'when variables defined but invalid' do
- let(:variables) do
- %w(VAR1 value1 VAR2 value2)
- end
-
- it 'raises error' do
- expect { subject }
- .to raise_error(GitlabCiYamlProcessor::ValidationError,
- /jobs:rspec:variables config should be a hash of key value pairs/)
- end
- end
-
- context 'when variables key defined but value not specified' do
- let(:variables) do
- nil
- end
-
- it 'returns empty array' do
- ##
- # When variables config is empty, we assume this is a valid
- # configuration, see issue #18775
- #
- expect(subject).to be_an_instance_of(Array)
- expect(subject).to be_empty
- end
- end
- end
- end
-
- context 'when job variables are not defined' do
- let(:config) do
- {
- before_script: ['pwd'],
- rspec: { script: 'rspec' }
- }
- end
-
- it 'returns empty array' do
- expect(subject).to be_an_instance_of(Array)
- expect(subject).to be_empty
- end
- end
- end
-
- describe "When" do
- %w(on_success on_failure always).each do |when_state|
- it "returns #{when_state} when defined" do
- config = YAML.dump({
- rspec: { script: "rspec", when: when_state }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- builds = config_processor.builds_for_stage_and_ref("test", "master")
- expect(builds.size).to eq(1)
- expect(builds.first[:when]).to eq(when_state)
- end
- end
- end
-
- describe 'cache' do
- context 'when cache definition has unknown keys' do
- it 'raises relevant validation error' do
- config = YAML.dump(
- { cache: { untracked: true, invalid: 'key' },
- rspec: { script: 'rspec' } })
-
- expect { GitlabCiYamlProcessor.new(config) }.to raise_error(
- GitlabCiYamlProcessor::ValidationError,
- 'cache config contains unknown keys: invalid'
- )
- end
- end
-
- it "returns cache when defined globally" do
- config = YAML.dump({
- cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
- rspec: {
- script: "rspec"
- }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config)
-
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
- paths: ["logs/", "binaries/"],
- untracked: true,
- key: 'key',
- policy: 'pull-push'
- )
- end
-
- it "returns cache when defined in a job" do
- config = YAML.dump({
- rspec: {
- cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
- script: "rspec"
- }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config)
-
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
- paths: ["logs/", "binaries/"],
- untracked: true,
- key: 'key',
- policy: 'pull-push'
- )
- end
-
- it "overwrite cache when defined for a job and globally" do
- config = YAML.dump({
- cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
- rspec: {
- script: "rspec",
- cache: { paths: ["test/"], untracked: false, key: 'local' }
- }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config)
-
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
- paths: ["test/"],
- untracked: false,
- key: 'local',
- policy: 'pull-push'
- )
- end
- end
-
- describe "Artifacts" do
- it "returns artifacts when defined" do
- config = YAML.dump({
- image: "ruby:2.1",
- services: ["mysql"],
- before_script: ["pwd"],
- rspec: {
- artifacts: {
- paths: ["logs/", "binaries/"],
- untracked: true,
- name: "custom_name",
- expire_in: "7d"
- },
- script: "rspec"
- }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config)
-
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- stage: "test",
- stage_idx: 1,
- name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
- options: {
- before_script: ["pwd"],
- script: ["rspec"],
- image: { name: "ruby:2.1" },
- services: [{ name: "mysql" }],
- artifacts: {
- name: "custom_name",
- paths: ["logs/", "binaries/"],
- untracked: true,
- expire_in: "7d"
- }
- },
- when: "on_success",
- allow_failure: false,
- environment: nil,
- yaml_variables: []
- })
- end
-
- %w[on_success on_failure always].each do |when_state|
- it "returns artifacts for when #{when_state} defined" do
- config = YAML.dump({
- rspec: {
- script: "rspec",
- artifacts: { paths: ["logs/", "binaries/"], when: when_state }
- }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
-
- builds = config_processor.builds_for_stage_and_ref("test", "master")
- expect(builds.size).to eq(1)
- expect(builds.first[:options][:artifacts][:when]).to eq(when_state)
- end
- end
- end
-
- describe '#environment' do
- let(:config) do
- {
- deploy_to_production: { stage: 'deploy', script: 'test', environment: environment }
- }
- end
-
- let(:processor) { GitlabCiYamlProcessor.new(YAML.dump(config)) }
- let(:builds) { processor.builds_for_stage_and_ref('deploy', 'master') }
-
- context 'when a production environment is specified' do
- let(:environment) { 'production' }
-
- 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
-
- context 'the url has a port as variable' do
- let(:environment) do
- { name: 'production',
- url: 'http://production.gitlab.com:$PORT' }
- end
-
- it 'allows a variable for the port' 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
- end
-
- context 'when no environment is specified' do
- let(:environment) { nil }
-
- it 'does return nil environment' do
- expect(builds.size).to eq(1)
- expect(builds.first[:environment]).to be_nil
- end
- end
-
- context 'is not a string' do
- let(:environment) { 1 }
-
- it 'raises error' do
- 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' }
-
- it 'raises error' do
- 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
-
- describe "Dependencies" do
- let(:config) do
- {
- build1: { stage: 'build', script: 'test' },
- build2: { stage: 'build', script: 'test' },
- test1: { stage: 'test', script: 'test', dependencies: dependencies },
- test2: { stage: 'test', script: 'test' },
- deploy: { stage: 'test', script: 'test' }
- }
- end
-
- subject { GitlabCiYamlProcessor.new(YAML.dump(config)) }
-
- context 'no dependencies' do
- let(:dependencies) { }
-
- it { expect { subject }.not_to raise_error }
- end
-
- context 'dependencies to builds' do
- let(:dependencies) { %w(build1 build2) }
-
- it { expect { subject }.not_to raise_error }
- end
-
- context 'dependencies to builds defined as symbols' do
- let(:dependencies) { [:build1, :build2] }
-
- it { expect { subject }.not_to raise_error }
- end
-
- context 'undefined dependency' do
- let(:dependencies) { ['undefined'] }
-
- it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: undefined dependency: undefined') }
- end
-
- context 'dependencies to deploy' do
- let(:dependencies) { ['deploy'] }
-
- it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: dependency deploy is not defined in prior stages') }
- end
- end
-
- describe "Hidden jobs" do
- let(:config_processor) { GitlabCiYamlProcessor.new(config) }
- subject { config_processor.builds_for_stage_and_ref("test", "master") }
-
- shared_examples 'hidden_job_handling' do
- it "doesn't create jobs that start with dot" do
- expect(subject.size).to eq(1)
- expect(subject.first).to eq({
- stage: "test",
- stage_idx: 1,
- name: "normal_job",
- commands: "test",
- coverage_regex: nil,
- tag_list: [],
- options: {
- script: ["test"]
- },
- when: "on_success",
- allow_failure: false,
- environment: nil,
- yaml_variables: []
- })
- end
- end
-
- context 'when hidden job have a script definition' do
- let(:config) do
- YAML.dump({
- '.hidden_job' => { image: 'ruby:2.1', script: 'test' },
- 'normal_job' => { script: 'test' }
- })
- end
-
- it_behaves_like 'hidden_job_handling'
- end
-
- context "when hidden job doesn't have a script definition" do
- let(:config) do
- YAML.dump({
- '.hidden_job' => { image: 'ruby:2.1' },
- 'normal_job' => { script: 'test' }
- })
- end
-
- it_behaves_like 'hidden_job_handling'
- end
- end
-
- describe "YAML Alias/Anchor" do
- let(:config_processor) { GitlabCiYamlProcessor.new(config) }
- subject { config_processor.builds_for_stage_and_ref("build", "master") }
-
- shared_examples 'job_templates_handling' do
- it "is correctly supported for jobs" do
- expect(subject.size).to eq(2)
- expect(subject.first).to eq({
- stage: "build",
- stage_idx: 0,
- name: "job1",
- commands: "execute-script-for-job",
- coverage_regex: nil,
- tag_list: [],
- options: {
- script: ["execute-script-for-job"]
- },
- when: "on_success",
- allow_failure: false,
- environment: nil,
- yaml_variables: []
- })
- expect(subject.second).to eq({
- stage: "build",
- stage_idx: 0,
- name: "job2",
- commands: "execute-script-for-job",
- coverage_regex: nil,
- tag_list: [],
- options: {
- script: ["execute-script-for-job"]
- },
- when: "on_success",
- allow_failure: false,
- environment: nil,
- yaml_variables: []
- })
- end
- end
-
- context 'when template is a job' do
- let(:config) do
- <<EOT
-job1: &JOBTMPL
- stage: build
- script: execute-script-for-job
-
-job2: *JOBTMPL
-EOT
- end
-
- it_behaves_like 'job_templates_handling'
- end
-
- context 'when template is a hidden job' do
- let(:config) do
- <<EOT
-.template: &JOBTMPL
- stage: build
- script: execute-script-for-job
-
-job1: *JOBTMPL
-
-job2: *JOBTMPL
-EOT
- end
-
- it_behaves_like 'job_templates_handling'
- end
-
- context 'when job adds its own keys to a template definition' do
- let(:config) do
- <<EOT
-.template: &JOBTMPL
- stage: build
-
-job1:
- <<: *JOBTMPL
- script: execute-script-for-job
-
-job2:
- <<: *JOBTMPL
- script: execute-script-for-job
-EOT
- end
-
- it_behaves_like 'job_templates_handling'
- end
- end
-
- describe "Error handling" do
- it "fails to parse YAML" do
- expect {GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
- end
-
- it "indicates that object is invalid" do
- expect {GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
- end
-
- it "returns errors if tags parameter is invalid" do
- config = YAML.dump({ rspec: { script: "test", tags: "mysql" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec tags should be an array of strings")
- end
-
- it "returns errors if before_script parameter is invalid" do
- config = YAML.dump({ before_script: "bundle update", rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script config should be an array of strings")
- end
-
- it "returns errors if job before_script parameter is not an array of strings" do
- config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array of strings")
- end
-
- it "returns errors if after_script parameter is invalid" do
- config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "after_script config should be an array of strings")
- end
-
- it "returns errors if job after_script parameter is not an array of strings" do
- config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array of strings")
- end
-
- it "returns errors if image parameter is invalid" do
- config = YAML.dump({ image: ["test"], rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image config should be a hash or a string")
- end
-
- it "returns errors if job name is blank" do
- config = YAML.dump({ '' => { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:job name can't be blank")
- end
-
- it "returns errors if job name is non-string" do
- config = YAML.dump({ 10 => { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:10 name should be a symbol")
- end
-
- it "returns errors if job image parameter is invalid" do
- config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a hash or a string")
- end
-
- it "returns errors if services parameter is not an array" do
- config = YAML.dump({ services: "test", rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be a array")
- end
-
- it "returns errors if services parameter is not an array of strings" do
- config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "service config should be a hash or a string")
- end
-
- it "returns errors if job services parameter is not an array" do
- config = YAML.dump({ rspec: { script: "test", services: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be a array")
- end
-
- it "returns errors if job services parameter is not an array of strings" do
- config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "service config should be a hash or a string")
- end
-
- it "returns error if job configuration is invalid" do
- config = YAML.dump({ extra: "bundle update" })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra config should be a hash")
- end
-
- it "returns errors if services configuration is not correct" do
- config = YAML.dump({ extra: { script: 'rspec', services: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be a array")
- end
-
- it "returns errors if there are no jobs defined" do
- config = YAML.dump({ before_script: ["bundle update"] })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs config should contain at least one visible job")
- end
-
- it "returns errors if there are no visible jobs defined" do
- config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs config should contain at least one visible job")
- end
-
- it "returns errors if job allow_failure parameter is not an boolean" do
- config = YAML.dump({ rspec: { script: "test", allow_failure: "string" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec allow failure should be a boolean value")
- end
-
- it "returns errors if job stage is not a string" do
- config = YAML.dump({ rspec: { script: "test", type: 1 } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:type config should be a string")
- end
-
- it "returns errors if job stage is not a pre-defined stage" do
- config = YAML.dump({ rspec: { script: "test", type: "acceptance" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
- end
-
- it "returns errors if job stage is not a defined stage" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", type: "acceptance" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test")
- end
-
- it "returns errors if stages is not an array" do
- config = YAML.dump({ stages: "test", rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages config should be an array of strings")
- end
-
- it "returns errors if stages is not an array of strings" do
- config = YAML.dump({ stages: [true, "test"], rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages config should be an array of strings")
- end
-
- it "returns errors if variables is not a map" do
- config = YAML.dump({ variables: "test", rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables config should be a hash of key value pairs")
- end
-
- it "returns errors if variables is not a map of key-value strings" do
- config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables config should be a hash of key value pairs")
- end
-
- it "returns errors if job when is not on_success, on_failure or always" do
- config = YAML.dump({ rspec: { script: "test", when: 1 } })
- expect do
- GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure, always or manual")
- end
-
- it "returns errors if job artifacts:name is not an a string" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { name: 1 } } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts name should be a string")
- end
-
- it "returns errors if job artifacts:when is not an a predefined value" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { when: 1 } } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts when should be on_success, on_failure or always")
- end
-
- it "returns errors if job artifacts:expire_in is not an a string" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: 1 } } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
- end
-
- it "returns errors if job artifacts:expire_in is not an a valid duration" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
- end
-
- it "returns errors if job artifacts:untracked is not an array of strings" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { untracked: "string" } } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts untracked should be a boolean value")
- end
-
- it "returns errors if job artifacts:paths is not an array of strings" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { paths: "string" } } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts paths should be an array of strings")
- end
-
- it "returns errors if cache:untracked is not an array of strings" do
- config = YAML.dump({ cache: { untracked: "string" }, rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:untracked config should be a boolean value")
- end
-
- it "returns errors if cache:paths is not an array of strings" do
- config = YAML.dump({ cache: { paths: "string" }, rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths config should be an array of strings")
- end
-
- it "returns errors if cache:key is not a string" do
- config = YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:key config should be a string or symbol")
- end
-
- it "returns errors if job cache:key is not an a string" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: 1 } } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:key config should be a string or symbol")
- end
-
- it "returns errors if job cache:untracked is not an array of strings" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { untracked: "string" } } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:untracked config should be a boolean value")
- end
-
- it "returns errors if job cache:paths is not an array of strings" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { paths: "string" } } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:paths config should be an array of strings")
- end
-
- it "returns errors if job dependencies is not an array of strings" do
- config = YAML.dump({ types: %w(build test), rspec: { script: "test", dependencies: "string" } })
- expect do
- GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings")
- end
- end
-
- describe "Validate configuration templates" do
- templates = Dir.glob("#{Rails.root.join('vendor/gitlab-ci-yml')}/**/*.gitlab-ci.yml")
-
- templates.each do |file|
- it "does not return errors for #{file}" do
- file = File.read(file)
-
- expect { GitlabCiYamlProcessor.new(file) }.not_to raise_error
- 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/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index f685bb83d0d..af1db2c3455 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -16,12 +16,32 @@ describe Gitlab::Auth do
expect(subject::DEFAULT_SCOPES).to eq [:api]
end
- it 'OPTIONAL_SCOPES contains all non-default scopes' do
- expect(subject::OPTIONAL_SCOPES).to eq %i[read_user read_registry openid]
+ it 'optional_scopes contains all non-default scopes' do
+ stub_container_registry_config(enabled: true)
+
+ expect(subject.optional_scopes).to eq %i[read_user read_registry openid]
end
- it 'REGISTRY_SCOPES contains all registry related scopes' do
- expect(subject::REGISTRY_SCOPES).to eq %i[read_registry]
+ context 'registry_scopes' do
+ context 'when registry is disabled' do
+ before do
+ stub_container_registry_config(enabled: false)
+ end
+
+ it 'is empty' do
+ expect(subject.registry_scopes).to eq []
+ end
+ end
+
+ context 'when registry is enabled' do
+ before do
+ stub_container_registry_config(enabled: true)
+ end
+
+ it 'contains all registry related scopes' do
+ expect(subject.registry_scopes).to eq %i[read_registry]
+ end
+ end
end
end
@@ -147,11 +167,17 @@ describe Gitlab::Auth do
expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, full_authentication_abilities))
end
- it 'succeeds for personal access tokens with the `read_registry` scope' do
- personal_access_token = create(:personal_access_token, scopes: ['read_registry'])
+ context 'when registry is enabled' do
+ before do
+ stub_container_registry_config(enabled: true)
+ end
+
+ it 'succeeds for personal access tokens with the `read_registry` scope' do
+ personal_access_token = create(:personal_access_token, scopes: ['read_registry'])
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
- expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, [:read_container_image]))
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
+ expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, [:read_container_image]))
+ end
end
it 'succeeds if it is an impersonation token' do
diff --git a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb
new file mode 100644
index 00000000000..5c471cbdeda
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::DeleteConflictingRedirectRoutesRange, :migration, schema: 20170907170235 do
+ let!(:redirect_routes) { table(:redirect_routes) }
+ let!(:routes) { table(:routes) }
+
+ before do
+ routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1')
+ routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2')
+ routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3')
+ routes.create!(id: 4, source_id: 4, source_type: 'Namespace', path: 'foo4')
+ routes.create!(id: 5, source_id: 5, source_type: 'Namespace', path: 'foo5')
+
+ # Valid redirects
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar2')
+ redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'bar3')
+
+ # Conflicting redirects
+ redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'foo1')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo2')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo3')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo4')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5')
+ end
+
+ it 'deletes the conflicting redirect_routes in the range' do
+ expect(redirect_routes.count).to eq(8)
+
+ expect do
+ described_class.new.perform(1, 3)
+ end.to change { redirect_routes.where("path like 'foo%'").count }.from(5).to(2)
+
+ expect do
+ described_class.new.perform(4, 5)
+ end.to change { redirect_routes.where("path like 'foo%'").count }.from(2).to(0)
+
+ expect(redirect_routes.count).to eq(3)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
index b155c20d8d3..cb52d971047 100644
--- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
@@ -215,9 +215,17 @@ end
# to a specific version of the database where said table is still present.
#
describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170825154015 do
+ let(:user_class) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'users'
+ end
+ end
+
let(:migration) { described_class.new }
- let(:project) { create(:project_empty_repo) }
- let(:author) { create(:user) }
+ let(:user_class) { table(:users) }
+ let(:author) { build(:user).becomes(user_class).tap(&:save!).becomes(User) }
+ let(:namespace) { create(:namespace, owner: author) }
+ let(:project) { create(:project_empty_repo, namespace: namespace, creator: author) }
# We can not rely on FactoryGirl as the state of Event may change in ways that
# the background migration does not expect, hence we use the Event class of
diff --git a/spec/lib/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index e49ecadde20..e6645985ba4 100644
--- a/spec/lib/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Ansi2html do
+describe Gitlab::Ci::Ansi2html do
subject { described_class }
it "prints non-ansi as-is" do
diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb
index f0769deef21..f8188675013 100644
--- a/spec/lib/ci/charts_spec.rb
+++ b/spec/lib/gitlab/ci/charts_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe Ci::Charts do
+describe Gitlab::Ci::Charts do
context "pipeline_times" do
let(:project) { create(:project) }
- let(:chart) { Ci::Charts::PipelineTime.new(project) }
+ let(:chart) { Gitlab::Ci::Charts::PipelineTime.new(project) }
subject { chart.pipeline_times }
diff --git a/spec/lib/ci/mask_secret_spec.rb b/spec/lib/gitlab/ci/mask_secret_spec.rb
index f7b753b022b..3789a142248 100644
--- a/spec/lib/ci/mask_secret_spec.rb
+++ b/spec/lib/gitlab/ci/mask_secret_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::MaskSecret do
+describe Gitlab::Ci::MaskSecret do
subject { described_class }
describe '#mask' do
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
new file mode 100644
index 00000000000..2278230f338
--- /dev/null
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -0,0 +1,1699 @@
+require 'spec_helper'
+
+module Gitlab
+ module Ci
+ describe YamlProcessor, :lib do
+ subject { described_class.new(config, path) }
+ let(:path) { 'path' }
+
+ describe 'our current .gitlab-ci.yml' do
+ let(:config) { File.read("#{Rails.root}/.gitlab-ci.yml") }
+
+ it 'is valid' do
+ error_message = described_class.validation_message(config)
+
+ expect(error_message).to be_nil
+ end
+ end
+
+ describe '#build_attributes' do
+ subject { described_class.new(config, path).build_attributes(:rspec) }
+
+ describe 'coverage entry' do
+ describe 'code coverage regexp' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec',
+ coverage: '/Code coverage: \d+\.\d+/' })
+ end
+
+ it 'includes coverage regexp in build attributes' do
+ expect(subject)
+ .to include(coverage_regex: 'Code coverage: \d+\.\d+')
+ end
+ end
+ end
+
+ describe 'retry entry' do
+ context 'when retry count is specified' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec', retry: 1 })
+ end
+
+ it 'includes retry count in build options attribute' do
+ expect(subject[:options]).to include(retry: 1)
+ end
+ end
+
+ context 'when retry count is not specified' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec' })
+ end
+
+ it 'does not persist retry count in the database' do
+ expect(subject[:options]).not_to have_key(:retry)
+ end
+ end
+ end
+
+ describe 'allow failure entry' do
+ context 'when job is a manual action' do
+ context 'when allow_failure is defined' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec',
+ when: 'manual',
+ allow_failure: false })
+ end
+
+ it 'is not allowed to fail' do
+ expect(subject[:allow_failure]).to be false
+ end
+ end
+
+ context 'when allow_failure is not defined' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec',
+ when: 'manual' })
+ end
+
+ it 'is allowed to fail' do
+ expect(subject[:allow_failure]).to be true
+ end
+ end
+ end
+
+ context 'when job is not a manual action' do
+ context 'when allow_failure is defined' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec',
+ allow_failure: false })
+ end
+
+ it 'is not allowed to fail' do
+ expect(subject[:allow_failure]).to be false
+ end
+ end
+
+ context 'when allow_failure is not defined' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec' })
+ end
+
+ it 'is not allowed to fail' do
+ expect(subject[:allow_failure]).to be false
+ end
+ end
+ end
+ end
+ end
+
+ describe '#stage_seeds' do
+ context 'when no refs policy is specified' do
+ let(:config) do
+ YAML.dump(production: { stage: 'deploy', script: 'cap prod' },
+ rspec: { stage: 'test', script: 'rspec' },
+ spinach: { stage: 'test', script: 'spinach' })
+ end
+
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ it 'correctly fabricates a stage seeds object' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 2
+ expect(seeds.first.stage[:name]).to eq 'test'
+ expect(seeds.second.stage[:name]).to eq 'deploy'
+ expect(seeds.first.builds.dig(0, :name)).to eq 'rspec'
+ expect(seeds.first.builds.dig(1, :name)).to eq 'spinach'
+ expect(seeds.second.builds.dig(0, :name)).to eq 'production'
+ end
+ end
+
+ context 'when refs policy is specified' do
+ let(:config) do
+ YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
+ spinach: { stage: 'test', script: 'spinach', only: ['tags'] })
+ end
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, ref: 'feature', tag: true)
+ end
+
+ it 'returns stage seeds only assigned to master to master' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 1
+ expect(seeds.first.stage[:name]).to eq 'test'
+ expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
+ end
+ end
+
+ context 'when source policy is specified' do
+ let(:config) do
+ YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
+ spinach: { stage: 'test', script: 'spinach', only: ['schedules'] })
+ end
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, source: :schedule)
+ end
+
+ it 'returns stage seeds only assigned to schedules' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 1
+ expect(seeds.first.stage[:name]).to eq 'test'
+ expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
+ end
+ end
+
+ context 'when kubernetes policy is specified' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ let(:config) do
+ YAML.dump(
+ spinach: { stage: 'test', script: 'spinach' },
+ production: {
+ stage: 'deploy',
+ script: 'cap',
+ only: { kubernetes: 'active' }
+ }
+ )
+ end
+
+ context 'when kubernetes is active' do
+ let(:project) { create(:kubernetes_project) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+ it 'returns seeds for kubernetes dependent job' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 2
+ expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
+ expect(seeds.second.builds.dig(0, :name)).to eq 'production'
+ end
+ end
+
+ context 'when kubernetes is not active' do
+ it 'does not return seeds for kubernetes dependent job' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 1
+ expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
+ end
+ end
+ end
+ end
+
+ describe "#builds_for_stage_and_ref" do
+ let(:type) { 'test' }
+
+ it "returns builds if no branch specified" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec" }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({
+ stage: "test",
+ stage_idx: 1,
+ name: "rspec",
+ commands: "pwd\nrspec",
+ coverage_regex: nil,
+ tag_list: [],
+ options: {
+ before_script: ["pwd"],
+ script: ["rspec"]
+ },
+ allow_failure: false,
+ when: "on_success",
+ environment: nil,
+ yaml_variables: []
+ })
+ end
+
+ describe 'only' do
+ it "does not return builds if only has another branch" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", only: ["deploy"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
+ end
+
+ it "does not return builds if only has regexp with another branch" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", only: ["/^deploy$/"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
+ end
+
+ it "returns builds if only has specified this branch" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", only: ["master"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
+ end
+
+ it "returns builds if only has a list of branches including specified" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: %w(master deploy) }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+ end
+
+ it "returns builds if only has a branches keyword specified" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: ["branches"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+ end
+
+ it "does not return builds if only has a tags keyword" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: ["tags"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+ end
+
+ it "returns builds if only has special keywords specified and source matches" do
+ possibilities = [{ keyword: 'pushes', source: 'push' },
+ { keyword: 'web', source: 'web' },
+ { keyword: 'triggers', source: 'trigger' },
+ { keyword: 'schedules', source: 'schedule' },
+ { keyword: 'api', source: 'api' },
+ { keyword: 'external', source: 'external' }]
+
+ possibilities.each do |possibility|
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(1)
+ end
+ end
+
+ it "does not return builds if only has special keywords specified and source doesn't match" do
+ possibilities = [{ keyword: 'pushes', source: 'web' },
+ { keyword: 'web', source: 'push' },
+ { keyword: 'triggers', source: 'schedule' },
+ { keyword: 'schedules', source: 'external' },
+ { keyword: 'api', source: 'trigger' },
+ { keyword: 'external', source: 'api' }]
+
+ possibilities.each do |possibility|
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(0)
+ end
+ end
+
+ it "returns builds if only has current repository path" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: ["branches@path"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+ end
+
+ it "does not return builds if only has different repository path" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: ["branches@fork"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+ end
+
+ it "returns build only for specified type" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: "test", only: %w(master deploy) },
+ staging: { script: "deploy", type: "deploy", only: %w(master deploy) },
+ production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, 'fork')
+
+ expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2)
+ expect(config_processor.builds_for_stage_and_ref("test", "deploy").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(1)
+ end
+
+ context 'for invalid value' do
+ let(:config) { { rspec: { script: "rspec", type: "test", only: only } } }
+ let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
+
+ context 'when it is integer' do
+ let(:only) { 1 }
+
+ it do
+ expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'jobs:rspec:only has to be either an array of conditions or a hash')
+ end
+ end
+
+ context 'when it is an array of integers' do
+ let(:only) { [1, 1] }
+
+ it do
+ expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'jobs:rspec:only config should be an array of strings or regexps')
+ end
+ end
+
+ context 'when it is invalid regex' do
+ let(:only) { ["/*invalid/"] }
+
+ it do
+ expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'jobs:rspec:only config should be an array of strings or regexps')
+ end
+ end
+ end
+ end
+
+ describe 'except' do
+ it "returns builds if except has another branch" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", except: ["deploy"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
+ end
+
+ it "returns builds if except has regexp with another branch" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", except: ["/^deploy$/"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
+ end
+
+ it "does not return builds if except has specified this branch" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", except: ["master"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
+ end
+
+ it "does not return builds if except has a list of branches including specified" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: %w(master deploy) }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+ end
+
+ it "does not return builds if except has a branches keyword specified" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: ["branches"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+ end
+
+ it "returns builds if except has a tags keyword" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: ["tags"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+ end
+
+ it "does not return builds if except has special keywords specified and source matches" do
+ possibilities = [{ keyword: 'pushes', source: 'push' },
+ { keyword: 'web', source: 'web' },
+ { keyword: 'triggers', source: 'trigger' },
+ { keyword: 'schedules', source: 'schedule' },
+ { keyword: 'api', source: 'api' },
+ { keyword: 'external', source: 'external' }]
+
+ possibilities.each do |possibility|
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(0)
+ end
+ end
+
+ it "returns builds if except has special keywords specified and source doesn't match" do
+ possibilities = [{ keyword: 'pushes', source: 'web' },
+ { keyword: 'web', source: 'push' },
+ { keyword: 'triggers', source: 'schedule' },
+ { keyword: 'schedules', source: 'external' },
+ { keyword: 'api', source: 'trigger' },
+ { keyword: 'external', source: 'api' }]
+
+ possibilities.each do |possibility|
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(1)
+ end
+ end
+
+ it "does not return builds if except has current repository path" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: ["branches@path"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+ end
+
+ it "returns builds if except has different repository path" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: ["branches@fork"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+ end
+
+ it "returns build except specified type" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@fork"] },
+ staging: { script: "deploy", type: "deploy", except: ["master"] },
+ production: { script: "deploy", type: "deploy", except: ["master@fork"] }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, 'fork')
+
+ expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2)
+ expect(config_processor.builds_for_stage_and_ref("test", "test").size).to eq(0)
+ expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(0)
+ end
+
+ context 'for invalid value' do
+ let(:config) { { rspec: { script: "rspec", except: except } } }
+ let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
+
+ context 'when it is integer' do
+ let(:except) { 1 }
+
+ it do
+ expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'jobs:rspec:except has to be either an array of conditions or a hash')
+ end
+ end
+
+ context 'when it is an array of integers' do
+ let(:except) { [1, 1] }
+
+ it do
+ expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'jobs:rspec:except config should be an array of strings or regexps')
+ end
+ end
+
+ context 'when it is invalid regex' do
+ let(:except) { ["/*invalid/"] }
+
+ it do
+ expect { processor }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'jobs:rspec:except config should be an array of strings or regexps')
+ end
+ end
+ end
+ end
+ end
+
+ describe "Scripts handling" do
+ let(:config_data) { YAML.dump(config) }
+ let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config_data, path) }
+
+ subject { config_processor.builds_for_stage_and_ref("test", "master").first }
+
+ describe "before_script" do
+ context "in global context" do
+ let(:config) do
+ {
+ before_script: ["global script"],
+ test: { script: ["script"] }
+ }
+ end
+
+ it "return commands with scripts concencaced" do
+ expect(subject[:commands]).to eq("global script\nscript")
+ end
+ end
+
+ context "overwritten in local context" do
+ let(:config) do
+ {
+ before_script: ["global script"],
+ test: { before_script: ["local script"], script: ["script"] }
+ }
+ end
+
+ it "return commands with scripts concencaced" do
+ expect(subject[:commands]).to eq("local script\nscript")
+ end
+ end
+ end
+
+ describe "script" do
+ let(:config) do
+ {
+ test: { script: ["script"] }
+ }
+ end
+
+ it "return commands with scripts concencaced" do
+ expect(subject[:commands]).to eq("script")
+ end
+ end
+
+ describe "after_script" do
+ context "in global context" do
+ let(:config) do
+ {
+ after_script: ["after_script"],
+ test: { script: ["script"] }
+ }
+ end
+
+ it "return after_script in options" do
+ expect(subject[:options][:after_script]).to eq(["after_script"])
+ end
+ end
+
+ context "overwritten in local context" do
+ let(:config) do
+ {
+ after_script: ["local after_script"],
+ test: { after_script: ["local after_script"], script: ["script"] }
+ }
+ end
+
+ it "return after_script in options" do
+ expect(subject[:options][:after_script]).to eq(["local after_script"])
+ end
+ end
+ end
+ end
+
+ describe "Image and service handling" do
+ context "when extended docker configuration is used" do
+ it "returns image and service when defined" do
+ config = YAML.dump({ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: ["mysql", { name: "docker:dind", alias: "docker",
+ entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] }],
+ before_script: ["pwd"],
+ rspec: { script: "rspec" } })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
+ stage: "test",
+ stage_idx: 1,
+ name: "rspec",
+ commands: "pwd\nrspec",
+ coverage_regex: nil,
+ tag_list: [],
+ options: {
+ before_script: ["pwd"],
+ script: ["rspec"],
+ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: [{ name: "mysql" },
+ { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] }]
+ },
+ allow_failure: false,
+ when: "on_success",
+ environment: nil,
+ yaml_variables: []
+ })
+ end
+
+ it "returns image and service when overridden for job" do
+ config = YAML.dump({ image: "ruby:2.1",
+ services: ["mysql"],
+ before_script: ["pwd"],
+ rspec: { image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: [{ name: "postgresql", alias: "db-pg",
+ entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] }, "docker:dind"],
+ script: "rspec" } })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
+ stage: "test",
+ stage_idx: 1,
+ name: "rspec",
+ commands: "pwd\nrspec",
+ coverage_regex: nil,
+ tag_list: [],
+ options: {
+ before_script: ["pwd"],
+ script: ["rspec"],
+ image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] },
+ { name: "docker:dind" }]
+ },
+ allow_failure: false,
+ when: "on_success",
+ environment: nil,
+ yaml_variables: []
+ })
+ end
+ end
+
+ context "when etended docker configuration is not used" do
+ it "returns image and service when defined" do
+ config = YAML.dump({ image: "ruby:2.1",
+ services: ["mysql", "docker:dind"],
+ before_script: ["pwd"],
+ rspec: { script: "rspec" } })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
+ stage: "test",
+ stage_idx: 1,
+ name: "rspec",
+ commands: "pwd\nrspec",
+ coverage_regex: nil,
+ tag_list: [],
+ options: {
+ before_script: ["pwd"],
+ script: ["rspec"],
+ image: { name: "ruby:2.1" },
+ services: [{ name: "mysql" }, { name: "docker:dind" }]
+ },
+ allow_failure: false,
+ when: "on_success",
+ environment: nil,
+ yaml_variables: []
+ })
+ end
+
+ it "returns image and service when overridden for job" do
+ config = YAML.dump({ image: "ruby:2.1",
+ services: ["mysql"],
+ before_script: ["pwd"],
+ rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
+ stage: "test",
+ stage_idx: 1,
+ name: "rspec",
+ commands: "pwd\nrspec",
+ coverage_regex: nil,
+ tag_list: [],
+ options: {
+ before_script: ["pwd"],
+ script: ["rspec"],
+ image: { name: "ruby:2.5" },
+ services: [{ name: "postgresql" }, { name: "docker:dind" }]
+ },
+ allow_failure: false,
+ when: "on_success",
+ environment: nil,
+ yaml_variables: []
+ })
+ end
+ end
+ end
+
+ describe 'Variables' do
+ let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), path) }
+
+ subject { config_processor.builds.first[:yaml_variables] }
+
+ context 'when global variables are defined' do
+ let(:variables) do
+ { 'VAR1' => 'value1', 'VAR2' => 'value2' }
+ end
+ let(:config) do
+ {
+ variables: variables,
+ before_script: ['pwd'],
+ rspec: { script: 'rspec' }
+ }
+ end
+
+ it 'returns global variables' do
+ expect(subject).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ end
+ end
+
+ context 'when job and global variables are defined' do
+ let(:global_variables) do
+ { 'VAR1' => 'global1', 'VAR3' => 'global3' }
+ end
+ let(:job_variables) do
+ { 'VAR1' => 'value1', 'VAR2' => 'value2' }
+ end
+ let(:config) do
+ {
+ before_script: ['pwd'],
+ variables: global_variables,
+ rspec: { script: 'rspec', variables: job_variables }
+ }
+ end
+
+ it 'returns all unique variables' do
+ expect(subject).to contain_exactly(
+ { key: 'VAR3', value: 'global3', public: true },
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ end
+ end
+
+ context 'when job variables are defined' do
+ let(:config) do
+ {
+ before_script: ['pwd'],
+ rspec: { script: 'rspec', variables: variables }
+ }
+ end
+
+ context 'when syntax is correct' do
+ let(:variables) do
+ { 'VAR1' => 'value1', 'VAR2' => 'value2' }
+ end
+
+ it 'returns job variables' do
+ expect(subject).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ end
+ end
+
+ context 'when syntax is incorrect' do
+ context 'when variables defined but invalid' do
+ let(:variables) do
+ %w(VAR1 value1 VAR2 value2)
+ end
+
+ it 'raises error' do
+ expect { subject }
+ .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ /jobs:rspec:variables config should be a hash of key value pairs/)
+ end
+ end
+
+ context 'when variables key defined but value not specified' do
+ let(:variables) do
+ nil
+ end
+
+ it 'returns empty array' do
+ ##
+ # When variables config is empty, we assume this is a valid
+ # configuration, see issue #18775
+ #
+ expect(subject).to be_an_instance_of(Array)
+ expect(subject).to be_empty
+ end
+ end
+ end
+ end
+
+ context 'when job variables are not defined' do
+ let(:config) do
+ {
+ before_script: ['pwd'],
+ rspec: { script: 'rspec' }
+ }
+ end
+
+ it 'returns empty array' do
+ expect(subject).to be_an_instance_of(Array)
+ expect(subject).to be_empty
+ end
+ end
+ end
+
+ describe "When" do
+ %w(on_success on_failure always).each do |when_state|
+ it "returns #{when_state} when defined" do
+ config = YAML.dump({
+ rspec: { script: "rspec", when: when_state }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ builds = config_processor.builds_for_stage_and_ref("test", "master")
+ expect(builds.size).to eq(1)
+ expect(builds.first[:when]).to eq(when_state)
+ end
+ end
+ end
+
+ describe 'cache' do
+ context 'when cache definition has unknown keys' do
+ it 'raises relevant validation error' do
+ config = YAML.dump(
+ { cache: { untracked: true, invalid: 'key' },
+ rspec: { script: 'rspec' } })
+
+ expect { Gitlab::Ci::YamlProcessor.new(config) }.to raise_error(
+ Gitlab::Ci::YamlProcessor::ValidationError,
+ 'cache config contains unknown keys: invalid'
+ )
+ end
+ end
+
+ it "returns cache when defined globally" do
+ config = YAML.dump({
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
+ rspec: {
+ script: "rspec"
+ }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
+ paths: ["logs/", "binaries/"],
+ untracked: true,
+ key: 'key',
+ policy: 'pull-push'
+ )
+ end
+
+ it "returns cache when defined in a job" do
+ config = YAML.dump({
+ rspec: {
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
+ script: "rspec"
+ }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
+ paths: ["logs/", "binaries/"],
+ untracked: true,
+ key: 'key',
+ policy: 'pull-push'
+ )
+ end
+
+ it "overwrite cache when defined for a job and globally" do
+ config = YAML.dump({
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
+ rspec: {
+ script: "rspec",
+ cache: { paths: ["test/"], untracked: false, key: 'local' }
+ }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
+ paths: ["test/"],
+ untracked: false,
+ key: 'local',
+ policy: 'pull-push'
+ )
+ end
+ end
+
+ describe "Artifacts" do
+ it "returns artifacts when defined" do
+ config = YAML.dump({
+ image: "ruby:2.1",
+ services: ["mysql"],
+ before_script: ["pwd"],
+ rspec: {
+ artifacts: {
+ paths: ["logs/", "binaries/"],
+ untracked: true,
+ name: "custom_name",
+ expire_in: "7d"
+ },
+ script: "rspec"
+ }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
+ stage: "test",
+ stage_idx: 1,
+ name: "rspec",
+ commands: "pwd\nrspec",
+ coverage_regex: nil,
+ tag_list: [],
+ options: {
+ before_script: ["pwd"],
+ script: ["rspec"],
+ image: { name: "ruby:2.1" },
+ services: [{ name: "mysql" }],
+ artifacts: {
+ name: "custom_name",
+ paths: ["logs/", "binaries/"],
+ untracked: true,
+ expire_in: "7d"
+ }
+ },
+ when: "on_success",
+ allow_failure: false,
+ environment: nil,
+ yaml_variables: []
+ })
+ end
+
+ %w[on_success on_failure always].each do |when_state|
+ it "returns artifacts for when #{when_state} defined" do
+ config = YAML.dump({
+ rspec: {
+ script: "rspec",
+ artifacts: { paths: ["logs/", "binaries/"], when: when_state }
+ }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config, path)
+
+ builds = config_processor.builds_for_stage_and_ref("test", "master")
+ expect(builds.size).to eq(1)
+ expect(builds.first[:options][:artifacts][:when]).to eq(when_state)
+ end
+ end
+ end
+
+ describe '#environment' do
+ let(:config) do
+ {
+ deploy_to_production: { stage: 'deploy', script: 'test', environment: environment }
+ }
+ end
+
+ let(:processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
+ let(:builds) { processor.builds_for_stage_and_ref('deploy', 'master') }
+
+ context 'when a production environment is specified' do
+ let(:environment) { 'production' }
+
+ 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
+
+ context 'the url has a port as variable' do
+ let(:environment) do
+ { name: 'production',
+ url: 'http://production.gitlab.com:$PORT' }
+ end
+
+ it 'allows a variable for the port' 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
+ end
+
+ context 'when no environment is specified' do
+ let(:environment) { nil }
+
+ it 'does return nil environment' do
+ expect(builds.size).to eq(1)
+ expect(builds.first[:environment]).to be_nil
+ end
+ end
+
+ context 'is not a string' do
+ let(:environment) { 1 }
+
+ it 'raises error' do
+ 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' }
+
+ it 'raises error' do
+ 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
+
+ describe "Dependencies" do
+ let(:config) do
+ {
+ build1: { stage: 'build', script: 'test' },
+ build2: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', script: 'test', dependencies: dependencies },
+ test2: { stage: 'test', script: 'test' },
+ deploy: { stage: 'test', script: 'test' }
+ }
+ end
+
+ subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
+
+ context 'no dependencies' do
+ let(:dependencies) { }
+
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'dependencies to builds' do
+ let(:dependencies) { %w(build1 build2) }
+
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'dependencies to builds defined as symbols' do
+ let(:dependencies) { [:build1, :build2] }
+
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'undefined dependency' do
+ let(:dependencies) { ['undefined'] }
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: undefined dependency: undefined') }
+ end
+
+ context 'dependencies to deploy' do
+ let(:dependencies) { ['deploy'] }
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: dependency deploy is not defined in prior stages') }
+ end
+ end
+
+ describe "Hidden jobs" do
+ let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) }
+ subject { config_processor.builds_for_stage_and_ref("test", "master") }
+
+ shared_examples 'hidden_job_handling' do
+ it "doesn't create jobs that start with dot" do
+ expect(subject.size).to eq(1)
+ expect(subject.first).to eq({
+ stage: "test",
+ stage_idx: 1,
+ name: "normal_job",
+ commands: "test",
+ coverage_regex: nil,
+ tag_list: [],
+ options: {
+ script: ["test"]
+ },
+ when: "on_success",
+ allow_failure: false,
+ environment: nil,
+ yaml_variables: []
+ })
+ end
+ end
+
+ context 'when hidden job have a script definition' do
+ let(:config) do
+ YAML.dump({
+ '.hidden_job' => { image: 'ruby:2.1', script: 'test' },
+ 'normal_job' => { script: 'test' }
+ })
+ end
+
+ it_behaves_like 'hidden_job_handling'
+ end
+
+ context "when hidden job doesn't have a script definition" do
+ let(:config) do
+ YAML.dump({
+ '.hidden_job' => { image: 'ruby:2.1' },
+ 'normal_job' => { script: 'test' }
+ })
+ end
+
+ it_behaves_like 'hidden_job_handling'
+ end
+ end
+
+ describe "YAML Alias/Anchor" do
+ let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) }
+ subject { config_processor.builds_for_stage_and_ref("build", "master") }
+
+ shared_examples 'job_templates_handling' do
+ it "is correctly supported for jobs" do
+ expect(subject.size).to eq(2)
+ expect(subject.first).to eq({
+ stage: "build",
+ stage_idx: 0,
+ name: "job1",
+ commands: "execute-script-for-job",
+ coverage_regex: nil,
+ tag_list: [],
+ options: {
+ script: ["execute-script-for-job"]
+ },
+ when: "on_success",
+ allow_failure: false,
+ environment: nil,
+ yaml_variables: []
+ })
+ expect(subject.second).to eq({
+ stage: "build",
+ stage_idx: 0,
+ name: "job2",
+ commands: "execute-script-for-job",
+ coverage_regex: nil,
+ tag_list: [],
+ options: {
+ script: ["execute-script-for-job"]
+ },
+ when: "on_success",
+ allow_failure: false,
+ environment: nil,
+ yaml_variables: []
+ })
+ end
+ end
+
+ context 'when template is a job' do
+ let(:config) do
+ <<EOT
+job1: &JOBTMPL
+ stage: build
+ script: execute-script-for-job
+
+job2: *JOBTMPL
+EOT
+ end
+
+ it_behaves_like 'job_templates_handling'
+ end
+
+ context 'when template is a hidden job' do
+ let(:config) do
+ <<EOT
+.template: &JOBTMPL
+ stage: build
+ script: execute-script-for-job
+
+job1: *JOBTMPL
+
+job2: *JOBTMPL
+EOT
+ end
+
+ it_behaves_like 'job_templates_handling'
+ end
+
+ context 'when job adds its own keys to a template definition' do
+ let(:config) do
+ <<EOT
+.template: &JOBTMPL
+ stage: build
+
+job1:
+ <<: *JOBTMPL
+ script: execute-script-for-job
+
+job2:
+ <<: *JOBTMPL
+ script: execute-script-for-job
+EOT
+ end
+
+ it_behaves_like 'job_templates_handling'
+ end
+ end
+
+ describe "Error handling" do
+ it "fails to parse YAML" do
+ expect {Gitlab::Ci::YamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
+ end
+
+ it "indicates that object is invalid" do
+ expect {Gitlab::Ci::YamlProcessor.new("invalid_yaml")}.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError)
+ end
+
+ it "returns errors if tags parameter is invalid" do
+ config = YAML.dump({ rspec: { script: "test", tags: "mysql" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec tags should be an array of strings")
+ end
+
+ it "returns errors if before_script parameter is invalid" do
+ config = YAML.dump({ before_script: "bundle update", rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "before_script config should be an array of strings")
+ end
+
+ it "returns errors if job before_script parameter is not an array of strings" do
+ config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array of strings")
+ end
+
+ it "returns errors if after_script parameter is invalid" do
+ config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "after_script config should be an array of strings")
+ end
+
+ it "returns errors if job after_script parameter is not an array of strings" do
+ config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array of strings")
+ end
+
+ it "returns errors if image parameter is invalid" do
+ config = YAML.dump({ image: ["test"], rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "image config should be a hash or a string")
+ end
+
+ it "returns errors if job name is blank" do
+ config = YAML.dump({ '' => { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:job name can't be blank")
+ end
+
+ it "returns errors if job name is non-string" do
+ config = YAML.dump({ 10 => { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:10 name should be a symbol")
+ end
+
+ it "returns errors if job image parameter is invalid" do
+ config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:image config should be a hash or a string")
+ end
+
+ it "returns errors if services parameter is not an array" do
+ config = YAML.dump({ services: "test", rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "services config should be a array")
+ end
+
+ it "returns errors if services parameter is not an array of strings" do
+ config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string")
+ end
+
+ it "returns errors if job services parameter is not an array" do
+ config = YAML.dump({ rspec: { script: "test", services: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:services config should be a array")
+ end
+
+ it "returns errors if job services parameter is not an array of strings" do
+ config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "service config should be a hash or a string")
+ end
+
+ it "returns error if job configuration is invalid" do
+ config = YAML.dump({ extra: "bundle update" })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:extra config should be a hash")
+ end
+
+ it "returns errors if services configuration is not correct" do
+ config = YAML.dump({ extra: { script: 'rspec', services: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:extra:services config should be a array")
+ end
+
+ it "returns errors if there are no jobs defined" do
+ config = YAML.dump({ before_script: ["bundle update"] })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs config should contain at least one visible job")
+ end
+
+ it "returns errors if there are no visible jobs defined" do
+ config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs config should contain at least one visible job")
+ end
+
+ it "returns errors if job allow_failure parameter is not an boolean" do
+ config = YAML.dump({ rspec: { script: "test", allow_failure: "string" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec allow failure should be a boolean value")
+ end
+
+ it "returns errors if job stage is not a string" do
+ config = YAML.dump({ rspec: { script: "test", type: 1 } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:type config should be a string")
+ end
+
+ it "returns errors if job stage is not a pre-defined stage" do
+ config = YAML.dump({ rspec: { script: "test", type: "acceptance" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
+ end
+
+ it "returns errors if job stage is not a defined stage" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", type: "acceptance" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "rspec job: stage parameter should be build, test")
+ end
+
+ it "returns errors if stages is not an array" do
+ config = YAML.dump({ stages: "test", rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "stages config should be an array of strings")
+ end
+
+ it "returns errors if stages is not an array of strings" do
+ config = YAML.dump({ stages: [true, "test"], rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "stages config should be an array of strings")
+ end
+
+ it "returns errors if variables is not a map" do
+ config = YAML.dump({ variables: "test", rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "variables config should be a hash of key value pairs")
+ end
+
+ it "returns errors if variables is not a map of key-value strings" do
+ config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "variables config should be a hash of key value pairs")
+ end
+
+ it "returns errors if job when is not on_success, on_failure or always" do
+ config = YAML.dump({ rspec: { script: "test", when: 1 } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config, path)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure, always or manual")
+ end
+
+ it "returns errors if job artifacts:name is not an a string" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { name: 1 } } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts name should be a string")
+ end
+
+ it "returns errors if job artifacts:when is not an a predefined value" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { when: 1 } } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts when should be on_success, on_failure or always")
+ end
+
+ it "returns errors if job artifacts:expire_in is not an a string" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: 1 } } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
+ end
+
+ it "returns errors if job artifacts:expire_in is not an a valid duration" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
+ end
+
+ it "returns errors if job artifacts:untracked is not an array of strings" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { untracked: "string" } } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts untracked should be a boolean value")
+ end
+
+ it "returns errors if job artifacts:paths is not an array of strings" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", artifacts: { paths: "string" } } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:artifacts paths should be an array of strings")
+ end
+
+ it "returns errors if cache:untracked is not an array of strings" do
+ config = YAML.dump({ cache: { untracked: "string" }, rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "cache:untracked config should be a boolean value")
+ end
+
+ it "returns errors if cache:paths is not an array of strings" do
+ config = YAML.dump({ cache: { paths: "string" }, rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "cache:paths config should be an array of strings")
+ end
+
+ it "returns errors if cache:key is not a string" do
+ config = YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "cache:key config should be a string or symbol")
+ end
+
+ it "returns errors if job cache:key is not an a string" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: 1 } } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:cache:key config should be a string or symbol")
+ end
+
+ it "returns errors if job cache:untracked is not an array of strings" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { untracked: "string" } } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:cache:untracked config should be a boolean value")
+ end
+
+ it "returns errors if job cache:paths is not an array of strings" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { paths: "string" } } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:cache:paths config should be an array of strings")
+ end
+
+ it "returns errors if job dependencies is not an array of strings" do
+ config = YAML.dump({ types: %w(build test), rspec: { script: "test", dependencies: "string" } })
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings")
+ end
+ end
+
+ describe "Validate configuration templates" do
+ templates = Dir.glob("#{Rails.root.join('vendor/gitlab-ci-yml')}/**/*.gitlab-ci.yml")
+
+ templates.each do |file|
+ it "does not return errors for #{file}" do
+ file = File.read(file)
+
+ expect { Gitlab::Ci::YamlProcessor.new(file) }.not_to raise_error
+ 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(Gitlab::Ci::YamlProcessor.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(Gitlab::Ci::YamlProcessor.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(Gitlab::Ci::YamlProcessor.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(Gitlab::Ci::YamlProcessor.validation_message(content)).to be_nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index cb430b47463..befdc18d1aa 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -47,7 +47,7 @@ describe Gitlab::DataBuilder::Push do
include_examples 'deprecated repository hook data'
it 'does not raise an error when given nil commits' do
- expect { described_class.build(spy, spy, spy, spy, spy, nil) }
+ expect { described_class.build(spy, spy, spy, spy, 'refs/tags/v1.1.0', nil) }
.not_to raise_error
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 1bcdc369c44..3c8350b3aad 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -914,4 +914,126 @@ describe Gitlab::Database::MigrationHelpers do
.to raise_error(RuntimeError, /Your database user is not allowed/)
end
end
+
+ describe '#bulk_queue_background_migration_jobs_by_range', :sidekiq do
+ context 'when the model has an ID column' do
+ let!(:id1) { create(:user).id }
+ let!(:id2) { create(:user).id }
+ let!(:id3) { create(:user).id }
+
+ before do
+ User.class_eval do
+ include EachBatch
+ end
+ end
+
+ context 'with enough rows to bulk queue jobs more than once' do
+ before do
+ stub_const('Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1)
+ end
+
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ end
+ end
+
+ it 'queues jobs in groups of buffer size 1' do
+ expect(BackgroundMigrationWorker).to receive(:perform_bulk).with([['FooJob', [id1, id2]]])
+ expect(BackgroundMigrationWorker).to receive(:perform_bulk).with([['FooJob', [id3, id3]]])
+
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+ end
+ end
+
+ context 'with not enough rows to bulk queue jobs more than once' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ end
+ end
+
+ it 'queues jobs in bulk all at once (big buffer size)' do
+ expect(BackgroundMigrationWorker).to receive(:perform_bulk).with([['FooJob', [id1, id2]],
+ ['FooJob', [id3, id3]]])
+
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+ end
+ end
+
+ context 'without specifying batch_size' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob')
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
+ end
+ end
+ end
+ end
+
+ context "when the model doesn't have an ID column" do
+ it 'raises error (for now)' do
+ expect do
+ model.bulk_queue_background_migration_jobs_by_range(ProjectAuthorization, 'FooJob')
+ end.to raise_error(StandardError, /does not have an ID/)
+ end
+ end
+ end
+
+ describe '#queue_background_migration_jobs_by_range_at_intervals', :sidekiq do
+ context 'when the model has an ID column' do
+ let!(:id1) { create(:user).id }
+ let!(:id2) { create(:user).id }
+ let!(:id3) { create(:user).id }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ before do
+ User.class_eval do
+ include EachBatch
+ end
+ end
+
+ context 'with batch_size option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.seconds, batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.seconds.from_now.to_f)
+ end
+ end
+ end
+
+ context 'without batch_size option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.seconds)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.seconds.from_now.to_f)
+ end
+ end
+ end
+ end
+
+ context "when the model doesn't have an ID column" do
+ it 'raises error (for now)' do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds)
+ end.to raise_error(StandardError, /does not have an ID/)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 8beebc10040..4fa30d8df8b 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -1761,17 +1761,9 @@ describe Gitlab::Diff::PositionTracer do
let(:merge_commit) do
update_file_again_commit
- committer = repository.user_to_committer(current_user)
-
- options = {
- message: "Merge branches",
- author: committer,
- committer: committer
- }
-
merge_request = create(:merge_request, source_branch: second_create_file_commit.sha, target_branch: branch_name, source_project: project)
- repository.merge(current_user, merge_request.diff_head_sha, merge_request, options)
+ repository.merge(current_user, merge_request.diff_head_sha, merge_request, "Merge branches")
project.commit(branch_name)
end
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 14d64d8c4da..46e968cc398 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -401,7 +401,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
end
- describe '#stats' do
+ shared_examples '#stats' do
subject { commit.stats }
describe '#additions' do
@@ -415,6 +415,14 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
end
+ describe '#stats with gitaly on' do
+ it_should_behave_like '#stats'
+ end
+
+ describe '#stats with gitaly disabled', skip_gitaly_mock: true do
+ it_should_behave_like '#stats'
+ end
+
describe '#to_diff' do
subject { commit.to_diff }
diff --git a/spec/lib/gitlab/git/hooks_service_spec.rb b/spec/lib/gitlab/git/hooks_service_spec.rb
index e9c0209fe3b..d4d75b66659 100644
--- a/spec/lib/gitlab/git/hooks_service_spec.rb
+++ b/spec/lib/gitlab/git/hooks_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Git::HooksService, seed_helper: true do
- let(:committer) { Gitlab::Git::Committer.new('Jane Doe', 'janedoe@example.com', 'user-456') }
+ let(:user) { Gitlab::Git::User.new('Jane Doe', 'janedoe@example.com', 'user-456') }
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, 'project-123') }
let(:service) { described_class.new }
@@ -18,7 +18,7 @@ describe Gitlab::Git::HooksService, seed_helper: true do
hook = double(trigger: [true, nil])
expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
- service.execute(committer, repository, @blankrev, @newrev, @ref) { }
+ service.execute(user, repository, @blankrev, @newrev, @ref) { }
end
end
@@ -28,7 +28,7 @@ describe Gitlab::Git::HooksService, seed_helper: true do
expect(service).not_to receive(:run_hook).with('post-receive')
expect do
- service.execute(committer, repository, @blankrev, @newrev, @ref)
+ service.execute(user, repository, @blankrev, @newrev, @ref)
end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
end
end
@@ -40,7 +40,7 @@ describe Gitlab::Git::HooksService, seed_helper: true do
expect(service).not_to receive(:run_hook).with('post-receive')
expect do
- service.execute(committer, repository, @blankrev, @newrev, @ref)
+ service.execute(user, repository, @blankrev, @newrev, @ref)
end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
end
end
diff --git a/spec/lib/gitlab/git/committer_spec.rb b/spec/lib/gitlab/git/user_spec.rb
index b0ddbb51449..0ebcecb26c0 100644
--- a/spec/lib/gitlab/git/committer_spec.rb
+++ b/spec/lib/gitlab/git/user_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Git::Committer do
+describe Gitlab::Git::User do
let(:name) { 'Jane Doe' }
let(:email) { 'janedoe@example.com' }
let(:gl_id) { 'user-123' }
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index 4702a978f19..494dfe0e595 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
require 'spec_helper'
describe Gitlab::Git do
@@ -29,4 +30,12 @@ describe Gitlab::Git do
end
end
end
+
+ describe '.ref_name' do
+ it 'ensure ref is a valid UTF-8 string' do
+ utf8_invalid_ref = Gitlab::Git::BRANCH_REF_PREFIX + "an_invalid_ref_\xE5"
+
+ expect(described_class.ref_name(utf8_invalid_ref)).to eq("an_invalid_ref_Ã¥")
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index f32fe5d8150..ec3abcb0953 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -165,4 +165,29 @@ describe Gitlab::GitalyClient::CommitService do
expect(subject).to eq("my diff")
end
end
+
+ describe '#commit_stats' do
+ let(:request) do
+ Gitaly::CommitStatsRequest.new(
+ repository: repository_message, revision: revision
+ )
+ end
+ let(:response) do
+ Gitaly::CommitStatsResponse.new(
+ oid: revision,
+ additions: 11,
+ deletions: 15
+ )
+ end
+
+ subject { described_class.new(repository).commit_stats(revision) }
+
+ it 'sends an RPC request' do
+ expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:commit_stats)
+ .with(request, kind_of(Hash)).and_return(response)
+
+ expect(subject.additions).to eq(11)
+ expect(subject.deletions).to eq(15)
+ end
+ end
end
diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/group_hierarchy_spec.rb
index 08010c2d0e2..8dc83a6db7f 100644
--- a/spec/lib/gitlab/group_hierarchy_spec.rb
+++ b/spec/lib/gitlab/group_hierarchy_spec.rb
@@ -23,6 +23,11 @@ describe Gitlab::GroupHierarchy, :postgresql do
expect(relation).to include(parent, child1, child2)
end
+
+ it 'does not allow the use of #update_all' do
+ expect { relation.update_all(share_with_group_lock: false) }
+ .to raise_error(ActiveRecord::ReadOnlyRecord)
+ end
end
describe '#base_and_descendants' do
@@ -43,6 +48,11 @@ describe Gitlab::GroupHierarchy, :postgresql do
expect(relation).to include(parent, child1, child2)
end
+
+ it 'does not allow the use of #update_all' do
+ expect { relation.update_all(share_with_group_lock: false) }
+ .to raise_error(ActiveRecord::ReadOnlyRecord)
+ end
end
describe '#all_groups' do
@@ -73,5 +83,10 @@ describe Gitlab::GroupHierarchy, :postgresql do
expect(relation).to include(child2)
end
+
+ it 'does not allow the use of #update_all' do
+ expect { relation.update_all(share_with_group_lock: false) }
+ .to raise_error(ActiveRecord::ReadOnlyRecord)
+ 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 8e3554375e8..d9b86e1bf34 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -119,7 +119,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
it 'has no when YML attributes but only the DB column' do
allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file).and_return(File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')))
- expect_any_instance_of(Ci::GitlabCiYamlProcessor).not_to receive(:build_attributes)
+ expect_any_instance_of(Gitlab::Ci::YamlProcessor).not_to receive(:build_attributes)
saved_project_json
end
diff --git a/spec/lib/gitlab/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
index d5f4da3ce36..dbcc200b90b 100644
--- a/spec/lib/gitlab/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
@@ -1,10 +1,11 @@
require 'spec_helper'
describe Gitlab::OAuth::AuthHash do
+ let(:provider) { 'ldap'.freeze }
let(:auth_hash) do
described_class.new(
OmniAuth::AuthHash.new(
- provider: provider_ascii,
+ provider: provider,
uid: uid_ascii,
info: info_hash
)
@@ -20,7 +21,6 @@ describe Gitlab::OAuth::AuthHash do
let(:last_name_raw) { "K\xC3\xBC\xC3\xA7\xC3\xBCk" }
let(:name_raw) { "Onur K\xC3\xBC\xC3\xA7\xC3\xBCk" }
- let(:provider_ascii) { 'ldap'.force_encoding(Encoding::ASCII_8BIT) }
let(:uid_ascii) { uid_raw.force_encoding(Encoding::ASCII_8BIT) }
let(:email_ascii) { email_raw.force_encoding(Encoding::ASCII_8BIT) }
let(:nickname_ascii) { nickname_raw.force_encoding(Encoding::ASCII_8BIT) }
@@ -28,7 +28,6 @@ describe Gitlab::OAuth::AuthHash do
let(:last_name_ascii) { last_name_raw.force_encoding(Encoding::ASCII_8BIT) }
let(:name_ascii) { name_raw.force_encoding(Encoding::ASCII_8BIT) }
- let(:provider_utf8) { provider_ascii.force_encoding(Encoding::UTF_8) }
let(:uid_utf8) { uid_ascii.force_encoding(Encoding::UTF_8) }
let(:email_utf8) { email_ascii.force_encoding(Encoding::UTF_8) }
let(:nickname_utf8) { nickname_ascii.force_encoding(Encoding::UTF_8) }
@@ -46,7 +45,7 @@ describe Gitlab::OAuth::AuthHash do
end
context 'defaults' do
- it { expect(auth_hash.provider).to eql provider_utf8 }
+ it { expect(auth_hash.provider).to eq provider }
it { expect(auth_hash.uid).to eql uid_utf8 }
it { expect(auth_hash.email).to eql email_utf8 }
it { expect(auth_hash.username).to eql nickname_utf8 }
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
new file mode 100644
index 00000000000..ecacea6bb35
--- /dev/null
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Gitlab::Themes, lib: true do
+ describe '.body_classes' do
+ it 'returns a space-separated list of class names' do
+ css = described_class.body_classes
+
+ expect(css).to include('ui_indigo')
+ expect(css).to include(' ui_dark ')
+ expect(css).to include(' ui_blue')
+ end
+ end
+
+ describe '.by_id' do
+ it 'returns a Theme by its ID' do
+ expect(described_class.by_id(1).name).to eq 'Indigo'
+ expect(described_class.by_id(3).name).to eq 'Light'
+ end
+ end
+
+ describe '.default' do
+ it 'returns the default application theme' do
+ allow(described_class).to receive(:default_id).and_return(2)
+ expect(described_class.default.id).to eq 2
+ end
+
+ it 'prevents an infinite loop when configuration default is invalid' do
+ default = described_class::APPLICATION_DEFAULT
+ themes = described_class::THEMES
+
+ config = double(default_theme: 0).as_null_object
+ allow(Gitlab).to receive(:config).and_return(config)
+ expect(described_class.default.id).to eq default
+
+ config = double(default_theme: themes.size + 5).as_null_object
+ allow(Gitlab).to receive(:config).and_return(config)
+ expect(described_class.default.id).to eq default
+ end
+ end
+
+ describe '.each' do
+ it 'passes the block to the THEMES Array' do
+ ids = []
+ described_class.each { |theme| ids << theme.id }
+ expect(ids).not_to be_empty
+ end
+ end
+end
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index fdc3990132a..59c28431e1e 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -174,4 +174,13 @@ describe Gitlab::UrlSanitizer do
end
end
end
+
+ context 'when credentials contains special chars' do
+ it 'should parse the URL without errors' do
+ url_sanitizer = described_class.new("https://foo:b?r@github.com/me/project.git")
+
+ expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git")
+ expect(url_sanitizer.full_url).to eq("https://foo:b?r@github.com/me/project.git")
+ end
+ end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 68429d792f2..c7d9f105f04 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -40,9 +40,13 @@ describe Gitlab::UsageData do
ci_builds
ci_internal_pipelines
ci_external_pipelines
+ ci_pipeline_config_auto_devops
+ ci_pipeline_config_repository
ci_runners
ci_triggers
ci_pipeline_schedules
+ auto_devops_enabled
+ auto_devops_disabled
deploy_keys
deployments
environments
diff --git a/spec/lib/system_check/orphans/namespace_check_spec.rb b/spec/lib/system_check/orphans/namespace_check_spec.rb
new file mode 100644
index 00000000000..2a61ff3ad65
--- /dev/null
+++ b/spec/lib/system_check/orphans/namespace_check_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+require 'rake_helper'
+
+describe SystemCheck::Orphans::NamespaceCheck do
+ let(:storages) { Gitlab.config.repositories.storages.reject { |key, _| key.eql? 'broken' } }
+
+ before do
+ allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
+ allow(subject).to receive(:fetch_disk_namespaces).and_return(disk_namespaces)
+ silence_output
+ end
+
+ describe '#multi_check' do
+ context 'all orphans' do
+ let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 repos/@hashed) }
+
+ it 'prints list of all orphaned namespaces except @hashed' do
+ expect_list_of_orphans(%w(orphan1 orphan2))
+
+ subject.multi_check
+ end
+ end
+
+ context 'few orphans with existing namespace' do
+ let!(:first_level) { create(:group, path: 'my-namespace') }
+ let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/@hashed) }
+
+ it 'prints list of orphaned namespaces' do
+ expect_list_of_orphans(%w(orphan1 orphan2))
+
+ subject.multi_check
+ end
+ end
+
+ context 'few orphans with existing namespace and parents with same name as orphans' do
+ let!(:first_level) { create(:group, path: 'my-namespace') }
+ let!(:second_level) { create(:group, path: 'second-level', parent: first_level) }
+ let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/second-level /repos/@hashed) }
+
+ it 'prints list of orphaned namespaces ignoring parents with same namespace as orphans' do
+ expect_list_of_orphans(%w(orphan1 orphan2 second-level))
+
+ subject.multi_check
+ end
+ end
+
+ context 'no orphans' do
+ let(:disk_namespaces) { %w(@hashed) }
+
+ it 'prints an empty list ignoring @hashed' do
+ expect_list_of_orphans([])
+
+ subject.multi_check
+ end
+ end
+ end
+
+ def expect_list_of_orphans(orphans)
+ expect(subject).to receive(:print_orphans).with(orphans, 'default')
+ end
+end
diff --git a/spec/lib/system_check/orphans/repository_check_spec.rb b/spec/lib/system_check/orphans/repository_check_spec.rb
new file mode 100644
index 00000000000..b0c2267d177
--- /dev/null
+++ b/spec/lib/system_check/orphans/repository_check_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+require 'rake_helper'
+
+describe SystemCheck::Orphans::RepositoryCheck do
+ let(:storages) { Gitlab.config.repositories.storages.reject { |key, _| key.eql? 'broken' } }
+
+ before do
+ allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
+ allow(subject).to receive(:fetch_disk_namespaces).and_return(disk_namespaces)
+ allow(subject).to receive(:fetch_disk_repositories).and_return(disk_repositories)
+ # silence_output
+ end
+
+ describe '#multi_check' do
+ context 'all orphans' do
+ let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 repos/@hashed) }
+ let(:disk_repositories) { %w(repo1.git repo2.git) }
+
+ it 'prints list of all orphaned namespaces except @hashed' do
+ expect_list_of_orphans(%w(orphan1/repo1.git orphan1/repo2.git orphan2/repo1.git orphan2/repo2.git))
+
+ subject.multi_check
+ end
+ end
+
+ context 'few orphans with existing namespace' do
+ let!(:first_level) { create(:group, path: 'my-namespace') }
+ let!(:project) { create(:project, path: 'repo', namespace: first_level) }
+ let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/@hashed) }
+ let(:disk_repositories) { %w(repo.git) }
+
+ it 'prints list of orphaned namespaces' do
+ expect_list_of_orphans(%w(orphan1/repo.git orphan2/repo.git))
+
+ subject.multi_check
+ end
+ end
+
+ context 'few orphans with existing namespace and parents with same name as orphans' do
+ let!(:first_level) { create(:group, path: 'my-namespace') }
+ let!(:second_level) { create(:group, path: 'second-level', parent: first_level) }
+ let!(:project) { create(:project, path: 'repo', namespace: first_level) }
+ let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/second-level /repos/@hashed) }
+ let(:disk_repositories) { %w(repo.git) }
+
+ it 'prints list of orphaned namespaces ignoring parents with same namespace as orphans' do
+ expect_list_of_orphans(%w(orphan1/repo.git orphan2/repo.git second-level/repo.git))
+
+ subject.multi_check
+ end
+ end
+
+ context 'no orphans' do
+ let(:disk_namespaces) { %w(@hashed) }
+ let(:disk_repositories) { %w(repo.git) }
+
+ it 'prints an empty list ignoring @hashed' do
+ expect_list_of_orphans([])
+
+ subject.multi_check
+ end
+ end
+ end
+
+ def expect_list_of_orphans(orphans)
+ expect(subject).to receive(:print_orphans).with(orphans, 'default')
+ end
+end
diff --git a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb
index 1396d12e5a9..759e77ac9db 100644
--- a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb
+++ b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170607121233_convert_custom_notification_settings_to_columns')
describe ConvertCustomNotificationSettingsToColumns, :migration do
+ let(:user_class) { table(:users) }
+
let(:settings_params) do
[
{ level: 0, events: [:new_note] }, # disabled, single event
@@ -19,7 +21,7 @@ describe ConvertCustomNotificationSettingsToColumns, :migration do
events[event] = true
end
- user = create(:user)
+ user = build(:user).becomes(user_class).tap(&:save!)
create_params = { user_id: user.id, level: params[:level], events: events }
notification_setting = described_class::NotificationSetting.create(create_params)
@@ -35,7 +37,7 @@ describe ConvertCustomNotificationSettingsToColumns, :migration do
events[event] = true
end
- user = create(:user)
+ user = build(:user).becomes(user_class).tap(&:save!)
create_params = events.merge(user_id: user.id, level: params[:level])
notification_setting = described_class::NotificationSetting.create(create_params)
diff --git a/spec/migrations/delete_conflicting_redirect_routes_spec.rb b/spec/migrations/delete_conflicting_redirect_routes_spec.rb
new file mode 100644
index 00000000000..1df2477da51
--- /dev/null
+++ b/spec/migrations/delete_conflicting_redirect_routes_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170907170235_delete_conflicting_redirect_routes')
+
+describe DeleteConflictingRedirectRoutes, :migration, :sidekiq do
+ let!(:redirect_routes) { table(:redirect_routes) }
+ let!(:routes) { table(:routes) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ before do
+ stub_const("DeleteConflictingRedirectRoutes::BATCH_SIZE", 2)
+ stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE", 2)
+
+ routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1')
+ routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2')
+ routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3')
+ routes.create!(id: 4, source_id: 4, source_type: 'Namespace', path: 'foo4')
+ routes.create!(id: 5, source_id: 5, source_type: 'Namespace', path: 'foo5')
+
+ # Valid redirects
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar2')
+ redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'bar3')
+
+ # Conflicting redirects
+ redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'foo1')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo2')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo3')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo4')
+ redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5')
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([described_class::MIGRATION, [1, 2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(12.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([described_class::MIGRATION, [3, 4]])
+ expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(24.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[2]['args']).to eq([described_class::MIGRATION, [5, 5]])
+ expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(36.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 3
+ end
+ end
+ end
+
+ it 'schedules background migrations' do
+ Sidekiq::Testing.inline! do
+ expect do
+ migrate!
+ end.to change { redirect_routes.count }.from(8).to(3)
+ end
+ end
+end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index d4da30b1641..f49a61062c1 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -1,8 +1,9 @@
require 'rails_helper'
-RSpec.describe AbuseReport do
- subject { create(:abuse_report) }
- let(:user) { create(:admin) }
+describe AbuseReport do
+ set(:report) { create(:abuse_report) }
+ set(:user) { create(:admin) }
+ subject { report }
it { expect(subject).to be_valid }
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index b5d5d58697b..49f44525b29 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-RSpec.describe Appearance do
+describe Appearance do
subject { build(:appearance) }
it { is_expected.to be_valid }
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index c7a9eabdf06..78cacf9ff5d 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -167,19 +167,33 @@ describe ApplicationSetting do
context 'housekeeping settings' do
it { is_expected.not_to allow_value(0).for(:housekeeping_incremental_repack_period) }
- it 'wants the full repack period to be longer than the incremental repack period' do
+ it 'wants the full repack period to be at least the incremental repack period' do
subject.housekeeping_incremental_repack_period = 2
subject.housekeeping_full_repack_period = 1
expect(subject).not_to be_valid
end
- it 'wants the gc period to be longer than the full repack period' do
- subject.housekeeping_full_repack_period = 2
- subject.housekeeping_gc_period = 1
+ it 'wants the gc period to be at least the full repack period' do
+ subject.housekeeping_full_repack_period = 100
+ subject.housekeeping_gc_period = 90
expect(subject).not_to be_valid
end
+
+ it 'allows the same period for incremental repack and full repack, effectively skipping incremental repack' do
+ subject.housekeeping_incremental_repack_period = 2
+ subject.housekeeping_full_repack_period = 2
+
+ expect(subject).to be_valid
+ end
+
+ it 'allows the same period for full repack and gc, effectively skipping full repack' do
+ subject.housekeeping_full_repack_period = 100
+ subject.housekeeping_gc_period = 100
+
+ expect(subject).to be_valid
+ end
end
end
diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb
index 8581bcbb08b..e89e534d914 100644
--- a/spec/models/chat_name_spec.rb
+++ b/spec/models/chat_name_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
describe ChatName do
- subject { create(:chat_name) }
+ set(:chat_name) { create(:chat_name) }
+ subject { chat_name }
it { is_expected.to belong_to(:service) }
it { is_expected.to belong_to(:user) }
diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb
index e0e5f73e6fe..70a9a206faa 100644
--- a/spec/models/chat_team_spec.rb
+++ b/spec/models/chat_team_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
describe ChatTeam do
- subject { create(:chat_team) }
+ set(:chat_team) { create(:chat_team) }
+ subject { chat_team }
# Associations
it { is_expected.to belong_to(:namespace) }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index c2c9f1c12d1..451968c7342 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1,18 +1,19 @@
require 'spec_helper'
describe Ci::Build do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:build) { create(:ci_build, pipeline: pipeline) }
- let(:test_trace) { 'This is a test' }
+ set(:user) { create(:user) }
+ set(:group) { create(:group, :access_requestable) }
+ set(:project) { create(:project, :repository, group: group) }
- let(:pipeline) do
+ set(:pipeline) do
create(:ci_pipeline, project: project,
sha: project.commit.id,
ref: project.default_branch,
status: 'success')
end
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
it { is_expected.to belong_to(:runner) }
it { is_expected.to belong_to(:trigger_request) }
it { is_expected.to belong_to(:erased_by) }
@@ -282,7 +283,7 @@ describe Ci::Build do
let(:project_regex) { '\(\d+\.\d+\) covered' }
before do
- project.build_coverage_regex = project_regex
+ project.update_column(:build_coverage_regex, project_regex)
end
context 'and coverage_regex attribute is not set' do
@@ -1096,9 +1097,6 @@ describe Ci::Build do
end
describe '#repo_url' do
- let(:build) { create(:ci_build) }
- let(:project) { build.project }
-
subject { build.repo_url }
it { is_expected.to be_a(String) }
@@ -1199,6 +1197,8 @@ describe Ci::Build do
end
context 'use from gitlab-ci.yml' do
+ let(:pipeline) { create(:ci_pipeline) }
+
before do
stub_ci_pipeline_yaml_file(config)
end
@@ -1442,11 +1442,7 @@ describe Ci::Build do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end
- let(:group) { create(:group, :access_requestable) }
-
before do
- build.project.update(group: group)
-
create(:ci_group_variable,
secret_variable.slice(:key, :value).merge(group: group))
end
@@ -1459,11 +1455,7 @@ describe Ci::Build do
{ key: 'PROTECTED_KEY', value: 'protected_value', public: false }
end
- let(:group) { create(:group, :access_requestable) }
-
before do
- build.project.update(group: group)
-
create(:ci_group_variable,
:protected,
protected_variable.slice(:key, :value).merge(group: group))
@@ -1486,6 +1478,10 @@ describe Ci::Build do
end
context 'when the ref is not protected' do
+ before do
+ build.update_column(:ref, 'some/feature')
+ end
+
it { is_expected.not_to include(protected_variable) }
end
end
@@ -1552,6 +1548,8 @@ describe Ci::Build do
end
context 'when yaml_variables are undefined' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
before do
build.yaml_variables = nil
end
@@ -1645,7 +1643,10 @@ describe Ci::Build do
before do
build.environment = 'production'
- allow(project).to receive(:deployment_variables).and_return([deployment_variable])
+
+ allow_any_instance_of(Project)
+ .to receive(:deployment_variables)
+ .and_return([deployment_variable])
end
it { is_expected.to include(deployment_variable) }
@@ -1669,14 +1670,19 @@ describe Ci::Build do
before do
allow(build).to receive(:predefined_variables) { [build_pre_var] }
- allow(project).to receive(:predefined_variables) { [project_pre_var] }
- allow(pipeline).to receive(:predefined_variables) { [pipeline_pre_var] }
allow(build).to receive(:yaml_variables) { [build_yaml_var] }
- allow(project).to receive(:secret_variables_for)
+ allow_any_instance_of(Project)
+ .to receive(:predefined_variables) { [project_pre_var] }
+
+ allow_any_instance_of(Project)
+ .to receive(:secret_variables_for)
.with(ref: 'master', environment: nil) do
[create(:ci_variable, key: 'secret', value: 'value')]
end
+
+ allow_any_instance_of(Ci::Pipeline)
+ .to receive(:predefined_variables) { [pipeline_pre_var] }
end
it do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 95da97b7bc5..77f0be6b120 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1439,4 +1439,24 @@ describe Ci::Pipeline, :mailer do
it_behaves_like 'not sending any notification'
end
end
+
+ describe '#latest_builds_with_artifacts' do
+ let!(:pipeline) { create(:ci_pipeline, :success) }
+
+ let!(:build) do
+ create(:ci_build, :success, :artifacts, pipeline: pipeline)
+ end
+
+ it 'returns the latest builds' do
+ expect(pipeline.latest_builds_with_artifacts).to eq([build])
+ end
+
+ it 'memoizes the returned relation' do
+ query_count = ActiveRecord::QueryRecorder
+ .new { 2.times { pipeline.latest_builds_with_artifacts.to_a } }
+ .count
+
+ expect(query_count).to eq(1)
+ end
+ end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 11e64a0f877..e3cfa149e3a 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -207,11 +207,6 @@ eos
context 'of a merge commit' do
let(:repository) { project.repository }
- let(:commit_options) do
- author = repository.user_to_committer(user)
- { message: 'Test message', committer: author, author: author }
- end
-
let(:merge_request) do
create(:merge_request,
source_branch: 'video',
@@ -224,7 +219,7 @@ eos
merge_commit_id = repository.merge(user,
merge_request.diff_head_sha,
merge_request,
- commit_options)
+ 'Test message')
repository.commit(merge_commit_id)
end
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index 9c99c3e5c08..fadc8bfeb61 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -140,18 +140,6 @@ describe GpgKey do
end
end
- describe 'notification', :mailer do
- let(:user) { create(:user) }
-
- it 'sends a notification' do
- perform_enqueued_jobs do
- create(:gpg_key, user: user)
- end
-
- should_email(user)
- end
- end
-
describe '#revoke' do
it 'invalidates all associated gpg signatures and destroys the key' do
gpg_key = create :gpg_key
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 96baeaff0a4..dbc4aba8547 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -169,16 +169,4 @@ describe Key, :mailer do
expect(described_class.new(key: " #{valid_key} ").key).to eq(valid_key)
end
end
-
- describe 'notification' do
- let(:user) { create(:user) }
-
- it 'sends a notification' do
- perform_enqueued_jobs do
- create(:key, user: user)
- end
-
- should_email(user)
- end
- end
end
diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb
index d24d4cf7695..0a3180f43e8 100644
--- a/spec/models/lfs_objects_project_spec.rb
+++ b/spec/models/lfs_objects_project_spec.rb
@@ -1,8 +1,11 @@
require 'spec_helper'
describe LfsObjectsProject do
- subject { create(:lfs_objects_project, project: project) }
- let(:project) { create(:project) }
+ set(:project) { create(:project) }
+
+ subject do
+ create(:lfs_objects_project, project: project)
+ end
describe 'associations' do
it { is_expected.to belong_to(:project) }
@@ -11,9 +14,13 @@ describe LfsObjectsProject do
describe 'validation' do
it { is_expected.to validate_presence_of(:lfs_object_id) }
- it { is_expected.to validate_uniqueness_of(:lfs_object_id).scoped_to(:project_id).with_message("already exists in project") }
-
it { is_expected.to validate_presence_of(:project_id) }
+
+ it 'validates object id' do
+ is_expected.to validate_uniqueness_of(:lfs_object_id)
+ .scoped_to(:project_id)
+ .with_message("already exists in project")
+ end
end
describe '#update_project_statistics' do
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index b2f2a3ce914..01440b15674 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -41,7 +41,7 @@ describe PersonalAccessToken do
it 'revokes the token' do
active_personal_access_token.revoke!
- expect(active_personal_access_token.revoked?).to be true
+ expect(active_personal_access_token).to be_revoked
end
end
@@ -61,10 +61,37 @@ describe PersonalAccessToken do
expect(personal_access_token).to be_valid
end
- it "allows creating a token with read_registry scope" do
- personal_access_token.scopes = [:read_registry]
+ context 'when registry is disabled' do
+ before do
+ stub_container_registry_config(enabled: false)
+ end
- expect(personal_access_token).to be_valid
+ it "rejects creating a token with read_registry scope" do
+ personal_access_token.scopes = [:read_registry]
+
+ expect(personal_access_token).not_to be_valid
+ expect(personal_access_token.errors[:scopes].first).to eq "can only contain available scopes"
+ end
+
+ it "allows revoking a token with read_registry scope" do
+ personal_access_token.scopes = [:read_registry]
+
+ personal_access_token.revoke!
+
+ expect(personal_access_token).to be_revoked
+ end
+ end
+
+ context 'when registry is enabled' do
+ before do
+ stub_container_registry_config(enabled: true)
+ end
+
+ it "allows creating a token with read_registry scope" do
+ personal_access_token.scopes = [:read_registry]
+
+ expect(personal_access_token).to be_valid
+ end
end
it "rejects creating a token with unavailable scopes" do
diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb
index 5faab9ba38b..be07ca2d945 100644
--- a/spec/models/project_services/pipelines_email_service_spec.rb
+++ b/spec/models/project_services/pipelines_email_service_spec.rb
@@ -6,7 +6,8 @@ describe PipelinesEmailService, :mailer do
end
let(:project) { create(:project, :repository) }
- let(:recipient) { 'test@gitlab.com' }
+ let(:recipients) { 'test@gitlab.com' }
+ let(:receivers) { [recipients] }
let(:data) do
Gitlab::DataBuilder::Pipeline.build(pipeline)
@@ -48,18 +49,24 @@ describe PipelinesEmailService, :mailer do
shared_examples 'sending email' do
before do
+ subject.recipients = recipients
+
perform_enqueued_jobs do
run
end
end
it 'sends email' do
- should_only_email(double(notification_email: recipient), kind: :bcc)
+ emails = receivers.map { |r| double(notification_email: r) }
+
+ should_only_email(*emails, kind: :bcc)
end
end
shared_examples 'not sending email' do
before do
+ subject.recipients = recipients
+
perform_enqueued_jobs do
run
end
@@ -75,10 +82,6 @@ describe PipelinesEmailService, :mailer do
subject.test(data)
end
- before do
- subject.recipients = recipient
- end
-
context 'when pipeline is failed' do
before do
data[:object_attributes][:status] = 'failed'
@@ -104,10 +107,6 @@ describe PipelinesEmailService, :mailer do
end
context 'with recipients' do
- before do
- subject.recipients = recipient
- end
-
context 'with failed pipeline' do
before do
data[:object_attributes][:status] = 'failed'
@@ -152,9 +151,7 @@ describe PipelinesEmailService, :mailer do
end
context 'with empty recipients list' do
- before do
- subject.recipients = ' ,, '
- end
+ let(:recipients) { ' ,, ' }
context 'with failed pipeline' do
before do
@@ -165,5 +162,19 @@ describe PipelinesEmailService, :mailer do
it_behaves_like 'not sending email'
end
end
+
+ context 'with recipients list separating with newlines' do
+ let(:recipients) { "\ntest@gitlab.com, \r\nexample@gitlab.com" }
+ let(:receivers) { %w[test@gitlab.com example@gitlab.com] }
+
+ context 'with failed pipeline' do
+ before do
+ data[:object_attributes][:status] = 'failed'
+ pipeline.update(status: 'failed')
+ end
+
+ it_behaves_like 'sending email'
+ end
+ end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 48fc77423ff..78226c6c3fa 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2682,4 +2682,60 @@ describe Project do
end
end
end
+
+ describe '#latest_successful_builds_for' do
+ let(:project) { build(:project) }
+
+ before do
+ allow(project).to receive(:default_branch).and_return('master')
+ end
+
+ context 'without a ref' do
+ it 'returns a pipeline for the default branch' do
+ expect(project)
+ .to receive(:latest_successful_pipeline_for_default_branch)
+
+ project.latest_successful_pipeline_for
+ end
+ end
+
+ context 'with the ref set to the default branch' do
+ it 'returns a pipeline for the default branch' do
+ expect(project)
+ .to receive(:latest_successful_pipeline_for_default_branch)
+
+ project.latest_successful_pipeline_for(project.default_branch)
+ end
+ end
+
+ context 'with a ref that is not the default branch' do
+ it 'returns the latest successful pipeline for the given ref' do
+ expect(project.pipelines).to receive(:latest_successful_for).with('foo')
+
+ project.latest_successful_pipeline_for('foo')
+ end
+ end
+ end
+
+ describe '#latest_successful_pipeline_for_default_branch' do
+ let(:project) { build(:project) }
+
+ before do
+ allow(project).to receive(:default_branch).and_return('master')
+ end
+
+ it 'memoizes and returns the latest successful pipeline for the default branch' do
+ pipeline = double(:pipeline)
+
+ expect(project.pipelines).to receive(:latest_successful_for)
+ .with(project.default_branch)
+ .and_return(pipeline)
+ .once
+
+ 2.times do
+ expect(project.latest_successful_pipeline_for_default_branch)
+ .to eq(pipeline)
+ end
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 7065d467ec0..60cd7e70055 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -8,12 +8,9 @@ describe Repository, models: true do
let(:repository) { project.repository }
let(:broken_repository) { create(:project, :broken_storage).repository }
let(:user) { create(:user) }
- let(:committer) { Gitlab::Git::Committer.from_user(user) }
+ let(:git_user) { Gitlab::Git::User.from_gitlab(user) }
- let(:commit_options) do
- author = repository.user_to_committer(user)
- { message: 'Test message', committer: author, author: author }
- end
+ let(:message) { 'Test message' }
let(:merge_commit) do
merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project)
@@ -21,7 +18,7 @@ describe Repository, models: true do
merge_commit_id = repository.merge(user,
merge_request.diff_head_sha,
merge_request,
- commit_options)
+ message)
repository.commit(merge_commit_id)
end
@@ -886,7 +883,7 @@ describe Repository, models: true do
context 'when pre hooks were successful' do
it 'runs without errors' do
expect_any_instance_of(Gitlab::Git::HooksService).to receive(:execute)
- .with(committer, repository.raw_repository, old_rev, blank_sha, 'refs/heads/feature')
+ .with(git_user, repository.raw_repository, old_rev, blank_sha, 'refs/heads/feature')
expect { repository.rm_branch(user, 'feature') }.not_to raise_error
end
@@ -932,20 +929,20 @@ describe Repository, models: true do
service = Gitlab::Git::HooksService.new
expect(Gitlab::Git::HooksService).to receive(:new).and_return(service)
expect(service).to receive(:execute)
- .with(committer, target_repository.raw_repository, old_rev, new_rev, updating_ref)
+ .with(git_user, target_repository.raw_repository, old_rev, new_rev, updating_ref)
.and_yield(service).and_return(true)
end
it 'runs without errors' do
expect do
- Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch('feature') do
+ Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
new_rev
end
end.not_to raise_error
end
it 'ensures the autocrlf Git option is set to :input' do
- service = Gitlab::Git::OperationService.new(committer, repository.raw_repository)
+ service = Gitlab::Git::OperationService.new(git_user, repository.raw_repository)
expect(service).to receive(:update_autocrlf_option)
@@ -956,7 +953,7 @@ describe Repository, models: true do
it 'updates the head' do
expect(repository.find_branch('feature').dereferenced_target.id).to eq(old_rev)
- Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch('feature') do
+ Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
new_rev
end
@@ -974,7 +971,7 @@ describe Repository, models: true do
expect(target_project.repository.raw_repository).to receive(:fetch_ref)
.and_call_original
- Gitlab::Git::OperationService.new(committer, target_repository.raw_repository)
+ Gitlab::Git::OperationService.new(git_user, target_repository.raw_repository)
.with_branch(
'master',
start_repository: project.repository.raw_repository,
@@ -990,7 +987,7 @@ describe Repository, models: true do
it 'does not fetch_ref and just pass the commit' do
expect(target_repository).not_to receive(:fetch_ref)
- Gitlab::Git::OperationService.new(committer, target_repository.raw_repository)
+ Gitlab::Git::OperationService.new(git_user, target_repository.raw_repository)
.with_branch('feature', start_repository: project.repository.raw_repository) { new_rev }
end
end
@@ -1009,7 +1006,7 @@ describe Repository, models: true do
end
expect do
- Gitlab::Git::OperationService.new(committer, target_project.repository.raw_repository)
+ Gitlab::Git::OperationService.new(git_user, target_project.repository.raw_repository)
.with_branch('feature',
start_repository: project.repository.raw_repository,
&:itself)
@@ -1031,7 +1028,7 @@ describe Repository, models: true do
repository.add_branch(user, branch, old_rev)
expect do
- Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch(branch) do
+ Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch(branch) do
new_rev
end
end.not_to raise_error
@@ -1049,7 +1046,7 @@ describe Repository, models: true do
# Updating 'master' to new_rev would lose the commits on 'master' that
# are not contained in new_rev. This should not be allowed.
expect do
- Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch(branch) do
+ Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch(branch) do
new_rev
end
end.to raise_error(Gitlab::Git::CommitError)
@@ -1061,7 +1058,7 @@ describe Repository, models: true do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
- Gitlab::Git::OperationService.new(committer, repository.raw_repository).with_branch('feature') do
+ Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
new_rev
end
end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
@@ -1287,10 +1284,7 @@ describe Repository, models: true do
describe '#merge' do
let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) }
- let(:commit_options) do
- author = repository.user_to_committer(user)
- { message: 'Test \r\n\r\n message', committer: author, author: author }
- end
+ let(:message) { 'Test \r\n\r\n message' }
it 'merges the code and returns the commit id' do
expect(merge_commit).to be_present
@@ -1298,19 +1292,19 @@ describe Repository, models: true do
end
it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do
- merge_commit_id = merge(repository, user, merge_request, commit_options)
+ merge_commit_id = merge(repository, user, merge_request, message)
expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id)
end
it 'removes carriage returns from commit message' do
- merge_commit_id = merge(repository, user, merge_request, commit_options)
+ merge_commit_id = merge(repository, user, merge_request, message)
- expect(repository.commit(merge_commit_id).message).to eq(commit_options[:message].delete("\r"))
+ expect(repository.commit(merge_commit_id).message).to eq(message.delete("\r"))
end
- def merge(repository, user, merge_request, options = {})
- repository.merge(user, merge_request.diff_head_sha, merge_request, options)
+ def merge(repository, user, merge_request, message)
+ repository.merge(user, merge_request.diff_head_sha, merge_request, message)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 3ba01313efb..c1affa812aa 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -716,6 +716,7 @@ describe User do
it "applies defaults to user" do
expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
+ expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
expect(user.external).to be_falsey
end
end
@@ -726,6 +727,7 @@ describe User do
it "applies defaults to user" do
expect(user.projects_limit).to eq(123)
expect(user.can_create_group).to be_falsey
+ expect(user.theme_id).to eq(1)
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 1583d1c2435..972e57bc373 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -22,7 +22,8 @@ describe API::Issues, :mailer do
state: :closed,
milestone: milestone,
created_at: generate(:past_time),
- updated_at: 3.hours.ago
+ updated_at: 3.hours.ago,
+ closed_at: 1.hour.ago
end
let!(:confidential_issue) do
create :issue,
@@ -738,6 +739,7 @@ describe API::Issues, :mailer do
expect(json_response['title']).to eq(issue.title)
expect(json_response['description']).to eq(issue.description)
expect(json_response['state']).to eq(issue.state)
+ expect(json_response['closed_at']).to be_falsy
expect(json_response['created_at']).to be_present
expect(json_response['updated_at']).to be_present
expect(json_response['labels']).to eq(issue.label_names)
@@ -748,6 +750,13 @@ describe API::Issues, :mailer do
expect(json_response['confidential']).to be_falsy
end
+ it "exposes the 'closed_at' attribute" do
+ get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['closed_at']).to be_present
+ end
+
context 'links exposure' do
it 'exposes related resources full URIs' do
get api("/projects/#{project.id}/issues/#{issue.iid}", user)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 21d2c9644fb..c4f6e97b915 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -28,10 +28,29 @@ describe API::MergeRequests do
describe 'GET /merge_requests' do
context 'when unauthenticated' do
- it 'returns authentication error' do
- get api('/merge_requests')
+ it 'returns an array of all merge requests' do
+ get api('/merge_requests', user), scope: 'all'
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ end
+
+ it "returns authentication error without any scope" do
+ get api("/merge_requests")
+
+ expect(response).to have_http_status(401)
+ end
+
+ it "returns authentication error when scope is assigned-to-me" do
+ get api("/merge_requests"), scope: 'assigned-to-me'
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to have_http_status(401)
+ end
+
+ it "returns authentication error when scope is created-by-me" do
+ get api("/merge_requests"), scope: 'created-by-me'
+
+ expect(response).to have_http_status(401)
end
end
@@ -134,10 +153,18 @@ describe API::MergeRequests do
describe "GET /projects/:id/merge_requests" do
context "when unauthenticated" do
- it "returns authentication error" do
+ it 'returns merge requests for public projects' do
+ get api("/projects/#{project.id}/merge_requests")
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ end
+
+ it "returns 404 for non public projects" do
+ project = create(:project, :private)
get api("/projects/#{project.id}/merge_requests")
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to have_http_status(404)
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 9602584f546..92e7d797cbd 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -54,9 +54,9 @@ describe API::Projects do
shared_examples_for 'projects response without N + 1 queries' do
it 'avoids N + 1 queries' do
- control_count = ActiveRecord::QueryRecorder.new do
+ control = ActiveRecord::QueryRecorder.new do
get api('/projects', current_user)
- end.count
+ end
if defined?(additional_project)
additional_project
@@ -66,7 +66,7 @@ describe API::Projects do
expect do
get api('/projects', current_user)
- end.not_to exceed_query_limit(control_count + 8)
+ end.not_to exceed_query_limit(control).with_threshold(8)
end
end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 48d99841385..7e174903918 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -1,10 +1,13 @@
require "spec_helper"
describe API::Services do
- let(:user) { create(:user) }
- let(:admin) { create(:admin) }
- let(:user2) { create(:user) }
- let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
+ set(:user) { create(:user) }
+ set(:admin) { create(:admin) }
+ set(:user2) { create(:user) }
+
+ set(:project) do
+ create(:project, creator_id: user.id, namespace: user.namespace)
+ end
Service.available_services_names.each do |service|
describe "PUT /projects/:id/services/#{service.dasherize}" do
@@ -98,8 +101,6 @@ describe API::Services do
end
describe 'POST /projects/:id/services/:slug/trigger' do
- let!(:project) { create(:project) }
-
describe 'Mattermost Service' do
let(:service_name) { 'mattermost_slash_commands' }
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 8d79ea3dd40..41bf43a9bce 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -49,6 +49,10 @@ describe JwtController do
let(:pat) { create(:personal_access_token, user: user, scopes: ['read_registry']) }
let(:headers) { { authorization: credentials('personal_access_token', pat.token) } }
+ before do
+ stub_container_registry_config(enabled: true)
+ end
+
subject! { get '/jwt/auth', parameters, headers }
it 'authenticates correctly' do
diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb
index 9a6875e448c..f4ff818c479 100644
--- a/spec/services/ci/pipeline_trigger_service_spec.rb
+++ b/spec/services/ci/pipeline_trigger_service_spec.rb
@@ -34,6 +34,8 @@ describe Ci::PipelineTriggerService do
expect(result[:pipeline].ref).to eq('master')
expect(result[:pipeline].project).to eq(project)
expect(result[:pipeline].user).to eq(trigger.owner)
+ expect(result[:pipeline].trigger_requests.to_a)
+ .to eq(result[:pipeline].builds.map(&:trigger_request).uniq)
expect(result[:status]).to eq(:success)
end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index bbc3a8c79f5..fbb3213f42b 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -1,9 +1,10 @@
require 'spec_helper'
describe Ci::RetryBuildService do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:pipeline) { create(:ci_pipeline, project: project) }
+
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:service) do
@@ -37,7 +38,7 @@ describe Ci::RetryBuildService do
:queued, :coverage, :tags, :allowed_to_fail, :on_tag,
:triggered, :trace, :teardown_environment,
description: 'my-job', stage: 'test', pipeline: pipeline,
- auto_canceled_by: create(:ci_empty_pipeline)) do |build|
+ auto_canceled_by: create(:ci_empty_pipeline, project: project)) do |build|
##
# TODO, workaround for FactoryGirl limitation when having both
# stage (text) and stage_id (integer) columns in the table.
diff --git a/spec/services/deploy_keys/create_service_spec.rb b/spec/services/deploy_keys/create_service_spec.rb
new file mode 100644
index 00000000000..7a604c0cadd
--- /dev/null
+++ b/spec/services/deploy_keys/create_service_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe DeployKeys::CreateService do
+ let(:user) { create(:user) }
+ let(:params) { attributes_for(:deploy_key) }
+
+ subject { described_class.new(user, params) }
+
+ it "creates a deploy key" do
+ expect { subject.execute }.to change { DeployKey.where(params.merge(user: user)).count }.by(1)
+ end
+end
diff --git a/spec/services/gpg_keys/create_service_spec.rb b/spec/services/gpg_keys/create_service_spec.rb
new file mode 100644
index 00000000000..20382a3a618
--- /dev/null
+++ b/spec/services/gpg_keys/create_service_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe GpgKeys::CreateService do
+ let(:user) { create(:user) }
+ let(:params) { attributes_for(:gpg_key) }
+
+ subject { described_class.new(user, params) }
+
+ context 'notification', :mailer do
+ it 'sends a notification' do
+ perform_enqueued_jobs do
+ subject.execute
+ end
+ should_email(user)
+ end
+ end
+
+ it 'creates a gpg key' do
+ expect { subject.execute }.to change { user.gpg_keys.where(params).count }.by(1)
+ end
+end
diff --git a/spec/services/keys/create_service_spec.rb b/spec/services/keys/create_service_spec.rb
new file mode 100644
index 00000000000..bcb436c1e46
--- /dev/null
+++ b/spec/services/keys/create_service_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Keys::CreateService do
+ let(:user) { create(:user) }
+ let(:params) { attributes_for(:key) }
+
+ subject { described_class.new(user, params) }
+
+ context 'notification', :mailer do
+ it 'sends a notification' do
+ perform_enqueued_jobs do
+ subject.execute
+ end
+ should_email(user)
+ end
+ end
+
+ it 'creates a key' do
+ expect { subject.execute }.to change { user.keys.where(params).count }.by(1)
+ end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 2af2485eeed..64e676f22a0 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -150,9 +150,7 @@ describe MergeRequests::RefreshService do
context 'manual merge of source branch' do
before do
# Merge master -> feature branch
- author = { email: 'test@gitlab.com', time: Time.now, name: "Me" }
- commit_options = { message: 'Test message', committer: author, author: author }
- @project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, commit_options)
+ @project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, 'Test message')
commit = @project.repository.commit('feature')
service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature')
reload_mrs
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 3e493148b32..f4b36eb7eeb 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -84,7 +84,6 @@ describe NotificationService, :mailer do
let!(:key) { create(:personal_key, key_options) }
it { expect(notification.new_key(key)).to be_truthy }
- it { should_email(key.user) }
describe 'never emails the ghost user' do
let(:key_options) { { user: User.ghost } }
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index 437c009e7fa..b7b5de07380 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -75,7 +75,7 @@ describe Projects::HousekeepingService do
end
end
- it 'uses all three kinds of housekeeping we offer' do
+ it 'goes through all three housekeeping tasks, executing only the highest task when there is overlap' do
allow(subject).to receive(:try_obtain_lease).and_return(:the_uuid)
allow(subject).to receive(:lease_key).and_return(:the_lease_key)
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 92cc9a37795..c551083ac90 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -57,6 +57,21 @@ describe Projects::UpdateService, '#execute' do
end
end
end
+
+ context 'When project visibility is higher than parent group' do
+ let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+
+ before do
+ project.update(namespace: group, visibility_level: group.visibility_level)
+ end
+
+ it 'does not update project visibility level' do
+ result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ expect(result).to eq({ status: :error, message: 'Visibility level public is not allowed in a internal group.' })
+ expect(project.reload).to be_internal
+ end
+ end
end
describe 'when updating project that has forks' do
@@ -148,7 +163,7 @@ describe Projects::UpdateService, '#execute' do
result = update_project(project, admin, path: 'existing')
expect(result).to include(status: :error)
- expect(result[:message]).to match('Project could not be updated!')
+ expect(result[:message]).to match('There is already a repository with that name on disk')
expect(project).not_to be_valid
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk')
@@ -159,8 +174,10 @@ describe Projects::UpdateService, '#execute' do
it 'returns an error result when record cannot be updated' do
result = update_project(project, admin, { name: 'foo&bar' })
- expect(result).to eq({ status: :error,
- message: 'Project could not be updated!' })
+ expect(result).to eq({
+ status: :error,
+ message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
+ })
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index ff1754fbe7e..92735336572 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,7 +1,7 @@
require './spec/simplecov_env'
SimpleCovEnv.start!
-ENV["RAILS_ENV"] ||= 'test'
+ENV["RAILS_ENV"] = 'test'
ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
require File.expand_path("../../config/environment", __FILE__)
diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb
index 3bd589d64b9..57e28e040d7 100644
--- a/spec/support/api/scopes/read_user_shared_examples.rb
+++ b/spec/support/api/scopes/read_user_shared_examples.rb
@@ -23,6 +23,10 @@ shared_examples_for 'allows the "read_user" scope' do
context 'when the requesting token does not have any required scope' do
let(:token) { create(:personal_access_token, scopes: ['read_registry'], user: user) }
+ before do
+ stub_container_registry_config(enabled: true)
+ end
+
it 'returns a "401" response' do
get api_call.call(path, user, personal_access_token: token)
diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json
index cd55d63125e..688175369ae 100644
--- a/spec/support/gitlab_stubs/session.json
+++ b/spec/support/gitlab_stubs/session.json
@@ -7,7 +7,7 @@
"skype":"aertert",
"linkedin":"",
"twitter":"",
- "color_scheme_id":2,
+ "theme_id":2,"color_scheme_id":2,
"state":"active",
"created_at":"2012-12-21T13:02:20Z",
"extern_uid":null,
diff --git a/spec/support/gitlab_stubs/user.json b/spec/support/gitlab_stubs/user.json
index cd55d63125e..ce8dfe5ae75 100644
--- a/spec/support/gitlab_stubs/user.json
+++ b/spec/support/gitlab_stubs/user.json
@@ -7,7 +7,7 @@
"skype":"aertert",
"linkedin":"",
"twitter":"",
- "color_scheme_id":2,
+ "theme_id":2,"color_scheme_id":2,
"state":"active",
"created_at":"2012-12-21T13:02:20Z",
"extern_uid":null,
@@ -17,4 +17,4 @@
"can_create_project":false,
"private_token":"Wvjy2Krpb7y8xi93owUz",
"access_token":"Wvjy2Krpb7y8xi93owUz"
-}
+} \ No newline at end of file
diff --git a/spec/support/matchers/navigation_matcher.rb b/spec/support/matchers/navigation_matcher.rb
new file mode 100644
index 00000000000..5b6d9c1a4df
--- /dev/null
+++ b/spec/support/matchers/navigation_matcher.rb
@@ -0,0 +1,6 @@
+RSpec::Matchers.define :have_active_navigation do |expected|
+ match do |page|
+ expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1)
+ expect(page.find('.sidebar-top-level-items > li.active')).to have_content(expected)
+ end
+end
diff --git a/spec/support/query_recorder.rb b/spec/support/query_recorder.rb
index 55b531b4cf7..ba0b805caad 100644
--- a/spec/support/query_recorder.rb
+++ b/spec/support/query_recorder.rb
@@ -34,15 +34,47 @@ RSpec::Matchers.define :exceed_query_limit do |expected|
supports_block_expectations
match do |block|
- query_count(&block) > expected
+ query_count(&block) > expected_count + threshold
end
failure_message_when_negated do |actual|
- "Expected a maximum of #{expected} queries, got #{@recorder.count}:\n\n#{@recorder.log_message}"
+ threshold_message = threshold > 0 ? " (+#{@threshold})" : ''
+ counts = "#{expected_count}#{threshold_message}"
+ "Expected a maximum of #{counts} queries, got #{actual_count}:\n\n#{log_message}"
+ end
+
+ def with_threshold(threshold)
+ @threshold = threshold
+ self
+ end
+
+ def threshold
+ @threshold.to_i
+ end
+
+ def expected_count
+ if expected.is_a?(ActiveRecord::QueryRecorder)
+ expected.count
+ else
+ expected
+ end
+ end
+
+ def actual_count
+ @recorder.count
end
def query_count(&block)
@recorder = ActiveRecord::QueryRecorder.new(&block)
@recorder.count
end
+
+ def log_message
+ if expected.is_a?(ActiveRecord::QueryRecorder)
+ extra_queries = (expected.log - @recorder.log).join("\n\n")
+ "Extra queries: \n\n #{extra_queries}"
+ else
+ @recorder.log_message
+ end
+ end
end
diff --git a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
new file mode 100644
index 00000000000..c92c7f603d6
--- /dev/null
+++ b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
@@ -0,0 +1,21 @@
+shared_examples 'issuable user dropdown behaviors' do
+ include FilteredSearchHelpers
+
+ before do
+ issuable # ensure we have at least one issuable
+ sign_in(user_in_dropdown)
+ end
+
+ %w[author assignee].each do |dropdown|
+ describe "#{dropdown} dropdown", :js do
+ it 'only includes members of the project/group' do
+ visit issuables_path
+
+ filtered_search.set("#{dropdown}:")
+
+ expect(find("#js-dropdown-#{dropdown} .filter-dropdown")).to have_content(user_in_dropdown.name)
+ expect(find("#js-dropdown-#{dropdown} .filter-dropdown")).not_to have_content(user_not_in_dropdown.name)
+ end
+ end
+ end
+end
diff --git a/spec/support/project_features_apply_to_issuables_shared_examples.rb b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
index 639b0924197..639b0924197 100644
--- a/spec/support/project_features_apply_to_issuables_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
diff --git a/spec/support/shared_examples/features/search_shared_examples.rb b/spec/support/shared_examples/features/search_shared_examples.rb
new file mode 100644
index 00000000000..25ebbf011d5
--- /dev/null
+++ b/spec/support/shared_examples/features/search_shared_examples.rb
@@ -0,0 +1,5 @@
+shared_examples 'top right search form' do
+ it 'does not show top right search form' do
+ expect(page).not_to have_selector('.search')
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 71b9deeabc3..6e5b9700b54 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -3,6 +3,8 @@ require 'rspec/mocks'
module TestEnv
extend self
+ ComponentFailedToInstallError = Class.new(StandardError)
+
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'signed-commits' => '2d1096e',
@@ -63,6 +65,11 @@ module TestEnv
# See gitlab.yml.example test section for paths
#
def init(opts = {})
+ unless Rails.env.test?
+ puts "\nTestEnv.init can only be run if `RAILS_ENV` is set to 'test' not '#{Rails.env}'!\n"
+ exit 1
+ end
+
# Disable mailer for spinach tests
disable_mailer if opts[:mailer] == false
@@ -122,50 +129,23 @@ module TestEnv
end
def setup_gitlab_shell
- puts "\n==> Setting up Gitlab Shell..."
- start = Time.now
- gitlab_shell_dir = Gitlab.config.gitlab_shell.path
- shell_needs_update = component_needs_update?(gitlab_shell_dir,
- Gitlab::Shell.version_required)
-
- unless !shell_needs_update || system('rake', 'gitlab:shell:install')
- puts "\nGitLab Shell failed to install, cleaning up #{gitlab_shell_dir}!\n"
- FileUtils.rm_rf(gitlab_shell_dir)
- exit 1
- end
-
- puts " GitLab Shell setup in #{Time.now - start} seconds...\n"
+ component_timed_setup('GitLab Shell',
+ install_dir: Gitlab.config.gitlab_shell.path,
+ version: Gitlab::Shell.version_required,
+ task: 'gitlab:shell:install')
end
def setup_gitaly
- puts "\n==> Setting up Gitaly..."
- start = Time.now
socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
gitaly_dir = File.dirname(socket_path)
- if gitaly_dir_stale?(gitaly_dir)
- puts " Gitaly is outdated, cleaning up #{gitaly_dir}!"
- FileUtils.rm_rf(gitaly_dir)
- end
-
- gitaly_needs_update = component_needs_update?(gitaly_dir,
- Gitlab::GitalyClient.expected_server_version)
+ component_timed_setup('Gitaly',
+ install_dir: gitaly_dir,
+ version: Gitlab::GitalyClient.expected_server_version,
+ task: "gitlab:gitaly:install[#{gitaly_dir}]") do
- unless !gitaly_needs_update || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
- puts "\nGitaly failed to install, cleaning up #{gitaly_dir}!\n"
- FileUtils.rm_rf(gitaly_dir)
- exit 1
+ start_gitaly(gitaly_dir)
end
-
- start_gitaly(gitaly_dir)
- puts " Gitaly setup in #{Time.now - start} seconds...\n"
- end
-
- def gitaly_dir_stale?(dir)
- gitaly_executable = File.join(dir, 'gitaly')
- return false unless File.exist?(gitaly_executable)
-
- File.mtime(gitaly_executable) < File.mtime(Rails.root.join('GITALY_SERVER_VERSION'))
end
def start_gitaly(gitaly_dir)
@@ -320,6 +300,40 @@ module TestEnv
end
end
+ def component_timed_setup(component, install_dir:, version:, task:)
+ puts "\n==> Setting up #{component}..."
+ start = Time.now
+
+ ensure_component_dir_name_is_correct!(component, install_dir)
+
+ if component_needs_update?(install_dir, version)
+ # Cleanup the component entirely to ensure we start fresh
+ FileUtils.rm_rf(install_dir)
+ unless system('rake', task)
+ raise ComponentFailedToInstallError
+ end
+ end
+
+ yield if block_given?
+
+ rescue ComponentFailedToInstallError
+ puts "\n#{component} failed to install, cleaning up #{install_dir}!\n"
+ FileUtils.rm_rf(install_dir)
+ exit 1
+ ensure
+ puts " #{component} setup in #{Time.now - start} seconds...\n"
+ end
+
+ def ensure_component_dir_name_is_correct!(component, path)
+ actual_component_dir_name = File.basename(path)
+ expected_component_dir_name = component.parameterize
+
+ unless actual_component_dir_name == expected_component_dir_name
+ puts " #{component} install dir should be named '#{expected_component_dir_name}', not '#{actual_component_dir_name}' (full install path given was '#{path}')!\n"
+ exit 1
+ end
+ end
+
def component_needs_update?(component_folder, expected_version)
version = File.read(File.join(component_folder, 'VERSION')).strip
diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb
index f2c19c7642a..7724d54c569 100644
--- a/spec/views/ci/lints/show.html.haml_spec.rb
+++ b/spec/views/ci/lints/show.html.haml_spec.rb
@@ -4,7 +4,7 @@ describe 'ci/lints/show' do
include Devise::Test::ControllerHelpers
describe 'XSS protection' do
- let(:config_processor) { Ci::GitlabCiYamlProcessor.new(YAML.dump(content)) }
+ let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(content)) }
before do
assign(:status, true)
assign(:builds, config_processor.builds)
@@ -59,7 +59,7 @@ describe 'ci/lints/show' do
}
end
- let(:config_processor) { Ci::GitlabCiYamlProcessor.new(YAML.dump(content)) }
+ let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(content)) }
context 'when the content is valid' do
before do
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index b17bc6692f3..c5f455b8948 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -1,16 +1,28 @@
require 'spec_helper'
describe 'layouts/nav/sidebar/_project' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ assign(:project, project)
+ assign(:repository, project.repository)
+ allow(view).to receive(:current_ref).and_return('master')
+
+ allow(view).to receive(:can?).and_return(true)
+ end
+
+ describe 'issue boards' do
+ it 'has boards tab when multiple issue boards available' do
+ render
+
+ expect(rendered).to have_css('a[title="Board"]')
+ end
+ end
+
describe 'container registry tab' do
before do
- project = create(:project, :repository)
stub_container_registry_config(enabled: true)
- assign(:project, project)
- assign(:repository, project.repository)
- allow(view).to receive(:current_ref).and_return('master')
-
- allow(view).to receive(:can?).and_return(true)
allow(controller).to receive(:controller_name)
.and_return('repositories')
allow(controller).to receive(:controller_path)
diff --git a/spec/views/shared/milestones/_issuable.html.haml.rb b/spec/views/shared/milestones/_issuable.html.haml.rb
new file mode 100644
index 00000000000..0a3f877cae0
--- /dev/null
+++ b/spec/views/shared/milestones/_issuable.html.haml.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe 'shared/milestones/_issuable.html.haml' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:issuable) { create(:issue, project: project, assignees: [user]) }
+
+ before do
+ assign(:project, project)
+ assign(:milestone, milestone)
+ end
+
+ it 'avatar links to issues page' do
+ render 'shared/milestones/issuable', issuable: issuable, show_project_name: true
+
+ expect(rendered).to have_css("a[href='#{project_issues_path(project, milestone_title: milestone.title, assignee_id: user.id, state: 'all')}']")
+ end
+end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 6f9ddb6c63c..f7b67b8efc6 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -31,8 +31,8 @@ describe GitGarbageCollectWorker do
expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
+ expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original
expect_any_instance_of(Gitlab::Git::Repository).to receive(:branch_count).and_call_original
- expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original
subject.perform(project.id, :gc, lease_key, lease_uuid)
end
@@ -77,8 +77,8 @@ describe GitGarbageCollectWorker do
expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
+ expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original
expect_any_instance_of(Gitlab::Git::Repository).to receive(:branch_count).and_call_original
- expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original
subject.perform(project.id)
end