summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2018-04-03 10:59:29 +0100
committerPhil Hughes <me@iamphill.com>2018-04-03 10:59:29 +0100
commitc0dddb511c3bedc9b07df97739a27e07354b2242 (patch)
tree3b1f6042c7fd274579aa82c81a3d5894b6e53d90 /spec
parent6bec91bfc9ec15556e833f4d8f441328d135638e (diff)
parent8dca091ff7f04bb92a7835ebeff783b7f0ef76cd (diff)
downloadgitlab-ce-c0dddb511c3bedc9b07df97739a27e07354b2242.tar.gz
Merge branch 'master' into ide-pending-tab
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb2
-rw-r--r--spec/factories/ci/build_metadata.rb9
-rw-r--r--spec/features/admin/admin_settings_spec.rb70
-rw-r--r--spec/features/groups/activity_spec.rb25
-rw-r--r--spec/features/groups/group_settings_spec.rb21
-rw-r--r--spec/features/groups/issues_spec.rb20
-rw-r--r--spec/features/groups/merge_requests_spec.rb16
-rw-r--r--spec/features/groups/show_spec.rb36
-rw-r--r--spec/features/groups/user_browse_projects_group_page_spec.rb29
-rw-r--r--spec/features/issuables/discussion_lock_spec.rb2
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb44
-rw-r--r--spec/features/profiles/account_spec.rb4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/project/export_status.json11
-rw-r--r--spec/helpers/page_layout_helper_spec.rb5
-rw-r--r--spec/javascripts/api_spec.js183
-rw-r--r--spec/javascripts/boards/mock_data.js49
-rw-r--r--spec/javascripts/droplab/constants_spec.js16
-rw-r--r--spec/javascripts/fixtures/projects.rb2
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js10
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js72
-rw-r--r--spec/javascripts/ide/components/changed_file_icon_spec.js3
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js58
-rw-r--r--spec/javascripts/ide/components/repo_tabs_spec.js2
-rw-r--r--spec/javascripts/ide/lib/common/model_spec.js14
-rw-r--r--spec/javascripts/ide/lib/editor_spec.js25
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js43
-rw-r--r--spec/javascripts/ide/stores/actions/merge_request_spec.js110
-rw-r--r--spec/javascripts/ide/stores/actions/tree_spec.js12
-rw-r--r--spec/javascripts/ide/stores/getters_spec.js22
-rw-r--r--spec/javascripts/ide/stores/mutations/file_spec.js26
-rw-r--r--spec/javascripts/ide/stores/mutations/merge_request_spec.js65
-rw-r--r--spec/javascripts/jobs/mock_data.js4
-rw-r--r--spec/javascripts/jobs/sidebar_detail_row_spec.js21
-rw-r--r--spec/javascripts/jobs/sidebar_details_block_spec.js6
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js18
-rw-r--r--spec/javascripts/notes/mock_data.js863
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js199
-rw-r--r--spec/javascripts/pages/labels/components/promote_label_modal_spec.js3
-rw-r--r--spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js3
-rw-r--r--spec/javascripts/pipelines/graph/mock_data.js467
-rw-r--r--spec/javascripts/registry/stores/actions_spec.js87
-rw-r--r--spec/javascripts/sidebar/confidential_issue_sidebar_spec.js18
-rw-r--r--spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js18
-rw-r--r--spec/javascripts/sidebar/mock_data.js59
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js244
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js387
-rw-r--r--spec/javascripts/vue_shared/components/mock_data.js2
-rw-r--r--spec/lib/backup/files_spec.rb66
-rw-r--r--spec/lib/backup/repository_spec.rb13
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb9
-rw-r--r--spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb72
-rw-r--r--spec/lib/gitlab/ci/build/step_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb91
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb8
-rw-r--r--spec/lib/gitlab/git/gitmodules_parser_spec.rb3
-rw-r--r--spec/lib/gitlab/git/hook_env_spec.rb (renamed from spec/lib/gitlab/git/env_spec.rb)71
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb20
-rw-r--r--spec/lib/gitlab/git/rev_list_spec.rb13
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb10
-rw-r--r--spec/lib/gitlab/gitaly_client/util_spec.rb11
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb6
-rw-r--r--spec/lib/gitlab/http_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb104
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb36
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb29
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb12
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb23
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb11
-rw-r--r--spec/mailers/previews/notify_preview.rb2
-rw-r--r--spec/models/ci/artifact_blob_spec.rb13
-rw-r--r--spec/models/ci/build_metadata_spec.rb61
-rw-r--r--spec/models/ci/build_spec.rb185
-rw-r--r--spec/models/ci/pipeline_spec.rb14
-rw-r--r--spec/models/clusters/applications/helm_spec.rb12
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb12
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb12
-rw-r--r--spec/models/clusters/applications/runner_spec.rb12
-rw-r--r--spec/models/clusters/cluster_spec.rb36
-rw-r--r--spec/models/concerns/chronic_duration_attribute_spec.rb115
-rw-r--r--spec/models/deploy_key_spec.rb21
-rw-r--r--spec/models/project_spec.rb51
-rw-r--r--spec/models/service_spec.rb15
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/requests/api/commits_spec.rb29
-rw-r--r--spec/requests/api/deploy_keys_spec.rb4
-rw-r--r--spec/requests/api/internal_spec.rb49
-rw-r--r--spec/requests/api/project_export_spec.rb79
-rw-r--r--spec/requests/api/runner_spec.rb59
-rw-r--r--spec/requests/api/runners_spec.rb5
-rw-r--r--spec/serializers/status_entity_spec.rb5
-rw-r--r--spec/services/ci/retry_build_service_spec.rb3
-rw-r--r--spec/services/issues/close_service_spec.rb4
-rw-r--r--spec/services/projects/create_service_spec.rb17
-rw-r--r--spec/services/projects/import_export/export_service_spec.rb85
-rw-r--r--spec/services/projects/import_service_spec.rb4
-rw-r--r--spec/support/cookie_helper.rb13
-rw-r--r--spec/support/gitaly.rb6
-rw-r--r--spec/support/login_helpers.rb4
-rw-r--r--spec/support/migrations_helpers.rb5
-rw-r--r--spec/support/shared_examples/uploaders/object_storage_shared_examples.rb16
-rw-r--r--spec/tasks/gitlab/uploads/migrate_rake_spec.rb129
-rw-r--r--spec/uploaders/object_storage_spec.rb24
-rw-r--r--spec/uploaders/workers/object_storage/background_move_worker_spec.rb146
-rw-r--r--spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb119
-rw-r--r--spec/workers/project_export_worker_spec.rb28
115 files changed, 4099 insertions, 1449 deletions
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 5b2614163ff..548c5ef36e7 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -107,7 +107,7 @@ describe Projects::MilestonesController do
it 'shows group milestone' do
post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
- expect(flash[:notice]).to eq("#{milestone.title} promoted to group milestone")
+ expect(flash[:notice]).to eq("#{milestone.title} promoted to <a href=\"#{group_milestone_path(project.group, milestone.iid)}\">group milestone</a>.")
expect(response).to redirect_to(project_milestones_path(project))
end
end
diff --git a/spec/factories/ci/build_metadata.rb b/spec/factories/ci/build_metadata.rb
new file mode 100644
index 00000000000..66bbd977b88
--- /dev/null
+++ b/spec/factories/ci/build_metadata.rb
@@ -0,0 +1,9 @@
+FactoryBot.define do
+ factory :ci_build_metadata, class: Ci::BuildMetadata do
+ build factory: :ci_build
+
+ after(:build) do |build_metadata, _|
+ build_metadata.project ||= build_metadata.build.project
+ end
+ end
+end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index c89bc54cad4..3005d74c3cf 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -120,6 +120,53 @@ feature 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
end
+ scenario 'Change Performance bar settings' do
+ group = create(:group)
+
+ page.within('.as-performance') do
+ check 'Enable the Performance Bar'
+ fill_in 'Allowed group', with: group.path
+ click_on 'Save changes'
+ end
+
+ expect(page).to have_content "Application settings saved successfully"
+ expect(find_field('Enable the Performance Bar')).to be_checked
+ expect(find_field('Allowed group').value).to eq group.path
+
+ page.within('.as-performance') do
+ uncheck 'Enable the Performance Bar'
+ click_on 'Save changes'
+ end
+
+ expect(page).to have_content 'Application settings saved successfully'
+ expect(find_field('Enable the Performance Bar')).not_to be_checked
+ expect(find_field('Allowed group').value).to be_nil
+ end
+
+ scenario 'Change Background jobs settings' do
+ page.within('.as-background') do
+ fill_in 'Throttling Factor', with: 1
+ click_button 'Save changes'
+ end
+
+ expect(Gitlab::CurrentSettings.sidekiq_throttling_factor).to eq(1)
+ expect(page).to have_content "Application settings saved successfully"
+ end
+
+ scenario 'Change Spam settings' do
+ page.within('.as-spam') do
+ check 'Enable reCAPTCHA'
+ fill_in 'reCAPTCHA Site Key', with: 'key'
+ fill_in 'reCAPTCHA Private Key', with: 'key'
+ fill_in 'IPs per user', with: 15
+ click_button 'Save changes'
+ end
+
+ expect(page).to have_content "Application settings saved successfully"
+ expect(Gitlab::CurrentSettings.recaptcha_enabled).to be true
+ expect(Gitlab::CurrentSettings.unique_ips_limit_per_user).to eq(15)
+ end
+
scenario 'Change Slack Notifications Service template settings' do
first(:link, 'Service Templates').click
click_link 'Slack notifications'
@@ -172,29 +219,6 @@ feature 'Admin updates settings' do
expect(find_field('ED25519 SSH keys').value).to eq(forbidden)
end
- scenario 'Change Performance Bar settings' do
- group = create(:group)
-
- check 'Enable the Performance Bar'
- fill_in 'Allowed group', with: group.path
-
- click_on 'Save'
-
- expect(page).to have_content 'Application settings saved successfully'
-
- expect(find_field('Enable the Performance Bar')).to be_checked
- expect(find_field('Allowed group').value).to eq group.path
-
- uncheck 'Enable the Performance Bar'
-
- click_on 'Save'
-
- expect(page).to have_content 'Application settings saved successfully'
-
- expect(find_field('Enable the Performance Bar')).not_to be_checked
- expect(find_field('Allowed group').value).to be_nil
- end
-
def check_all_events
page.check('Active')
page.check('Push')
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index d3b25ec3d6c..7bc809b3104 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -8,11 +8,30 @@ feature 'Group activity page' do
context 'when signed in' do
before do
sign_in(user)
- visit path
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ describe 'RSS' do
+ before do
+ visit path
+ end
+
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ end
+
+ context 'when project is in the group', :js do
+ let(:project) { create(:project, :public, namespace: group) }
+
+ before do
+ project.add_master(user)
+
+ visit path
+ end
+
+ it 'renders user joined to project event' do
+ expect(page).to have_content 'joined project'
+ end
+ end
end
context 'when signed out' do
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index b83bad3befb..1ce30015e81 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -76,6 +76,27 @@ feature 'Edit group settings' do
end
end
end
+
+ describe 'edit group avatar' do
+ before do
+ visit edit_group_path(group)
+
+ attach_file(:group_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif'))
+
+ expect { click_button 'Save group' }.to change { group.reload.avatar? }.to(true)
+ end
+
+ it 'uploads new group avatar' do
+ expect(group.avatar).to be_instance_of AvatarUploader
+ expect(group.avatar.url).to eq "/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif"
+ expect(page).to have_link('Remove avatar')
+ end
+
+ it 'removes group avatar' do
+ expect { click_link 'Remove avatar' }.to change { group.reload.avatar? }.to(false)
+ expect(page).not_to have_link('Remove avatar')
+ end
+ end
end
def update_path(new_group_path)
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 450bc0ff8cf..90bf7ba49f6 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -3,8 +3,11 @@ require 'spec_helper'
feature 'Group issues page' do
include FilteredSearchHelpers
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :public, group: group)}
+ let(:path) { issues_group_path(group) }
+
context 'with shared examples' do
- let(:path) { issues_group_path(group) }
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
include_examples 'project features apply to issuables', Issue
@@ -31,7 +34,6 @@ feature 'Group issues page' do
let(:access_level) { ProjectFeature::ENABLED }
let(:user) { user_in_group }
let(:user2) { user_outside_group }
- let(:path) { issues_group_path(group) }
it 'filters by only group users' do
filtered_search.set('assignee:')
@@ -43,9 +45,7 @@ feature 'Group issues page' do
end
context 'issues list', :nested_groups do
- let(:group) { create(:group)}
let(:subgroup) { create(:group, parent: group) }
- let(:project) { create(:project, :public, group: group)}
let(:subgroup_project) { create(:project, :public, group: subgroup)}
let!(:issue) { create(:issue, project: project, title: 'root group issue') }
let!(:subgroup_issue) { create(:issue, project: subgroup_project, title: 'subgroup issue') }
@@ -59,5 +59,17 @@ feature 'Group issues page' do
expect(page).to have_content('subgroup issue')
end
end
+
+ context 'when project is archived' do
+ before do
+ project.archive!
+ end
+
+ it 'does not render issue' do
+ visit path
+
+ expect(page).not_to have_content issue.title[0..80]
+ end
+ end
end
end
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index 7ce6a61d50c..672ae785c2d 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -5,14 +5,14 @@ feature 'Group merge requests page' do
let(:path) { merge_requests_group_path(group) }
let(:issuable) { create(:merge_request, source_project: project, target_project: project, title: 'this is my created issuable') }
+ let(:access_level) { ProjectFeature::ENABLED }
+ let(:user) { user_in_group }
include_examples 'project features apply to issuables', MergeRequest
context 'archived issuable' do
let(:project_archived) { create(:project, :archived, :merge_requests_enabled, :repository, group: group) }
let(:issuable_archived) { create(:merge_request, source_project: project_archived, target_project: project_archived, title: 'issuable of an archived project') }
- let(:access_level) { ProjectFeature::ENABLED }
- let(:user) { user_in_group }
before do
issuable_archived
@@ -36,9 +36,17 @@ feature 'Group merge requests page' do
end
end
+ context 'when merge request assignee to user' do
+ before do
+ issuable.update!(assignee: user)
+
+ visit path
+ end
+
+ it { expect(page).to have_content issuable.title[0..80] }
+ end
+
context 'group filtered search', :js do
- let(:access_level) { ProjectFeature::ENABLED }
- let(:user) { user_in_group }
let(:user2) { user_outside_group }
it 'filters by assignee only group users' do
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index ceccc471405..4ffadbbcd35 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -15,14 +15,44 @@ feature 'Group show page' do
end
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+
+ context 'when group does not exist' do
+ let(:path) { group_path('not-exist') }
+
+ it { expect(status_code).to eq(404) }
+ end
end
context 'when signed out' do
- before do
- visit path
+ describe 'RSS' do
+ before do
+ visit path
+ end
+
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ end
+
+ context 'when group has a public project', :js do
+ let!(:project) { create(:project, :public, namespace: group) }
+
+ it 'renders public project' do
+ visit path
+
+ expect(page).to have_link group.name
+ expect(page).to have_link project.name
+ end
end
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ context 'when group has a private project', :js do
+ let!(:project) { create(:project, :private, namespace: group) }
+
+ it 'does not render private project' do
+ visit path
+
+ expect(page).to have_link group.name
+ expect(page).not_to have_link project.name
+ end
+ end
end
context 'subgroup support' do
diff --git a/spec/features/groups/user_browse_projects_group_page_spec.rb b/spec/features/groups/user_browse_projects_group_page_spec.rb
new file mode 100644
index 00000000000..e81c3180e78
--- /dev/null
+++ b/spec/features/groups/user_browse_projects_group_page_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+describe 'User browse group projects page' do
+ let(:user) { create :user }
+ let(:group) { create :group }
+
+ context 'when user is owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ context 'when user signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'when group has archived project', :js do
+ let!(:project) { create :project, :archived, namespace: group }
+
+ it 'renders projects list' do
+ visit projects_group_path(group)
+
+ expect(page).to have_link project.name
+ expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/issuables/discussion_lock_spec.rb b/spec/features/issuables/discussion_lock_spec.rb
index ecbe51a7bc2..7ea29ff252b 100644
--- a/spec/features/issuables/discussion_lock_spec.rb
+++ b/spec/features/issuables/discussion_lock_spec.rb
@@ -14,7 +14,7 @@ describe 'Discussion Lock', :js do
project.add_developer(user)
end
- context 'when the discussion is unlocked' do
+ context 'when the discussion is unlocked' do
it 'the user can lock the issue' do
visit project_issue_path(project, issue)
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index b835558b142..27551bb70ee 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -161,6 +161,50 @@ feature 'Issue Sidebar' do
end
end
end
+
+ context 'interacting with collapsed sidebar', :js do
+ collapsed_sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed'
+ expanded_sidebar_selector = 'aside.right-sidebar.right-sidebar-expanded'
+ confidentiality_sidebar_block = '.block.confidentiality'
+ lock_sidebar_block = '.block.lock'
+ collapsed_sidebar_block_icon = '.sidebar-collapsed-icon'
+
+ before do
+ resize_screen_sm
+ end
+
+ it 'confidentiality block expands then collapses sidebar' do
+ expect(page).to have_css(collapsed_sidebar_selector)
+
+ page.within(confidentiality_sidebar_block) do
+ find(collapsed_sidebar_block_icon).click
+ end
+
+ expect(page).to have_css(expanded_sidebar_selector)
+
+ page.within(confidentiality_sidebar_block) do
+ page.find('button', text: 'Cancel').click
+ end
+
+ expect(page).to have_css(collapsed_sidebar_selector)
+ end
+
+ it 'lock block expands then collapses sidebar' do
+ expect(page).to have_css(collapsed_sidebar_selector)
+
+ page.within(lock_sidebar_block) do
+ find(collapsed_sidebar_block_icon).click
+ end
+
+ expect(page).to have_css(expanded_sidebar_selector)
+
+ page.within(lock_sidebar_block) do
+ page.find('button', text: 'Cancel').click
+ end
+
+ expect(page).to have_css(collapsed_sidebar_selector)
+ end
+ end
end
context 'as a guest' do
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 171e061e60e..e8eb0d17ca4 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -43,14 +43,14 @@ feature 'Profile > Account' do
update_username(new_username)
visit new_project_path
expect(current_path).to eq(new_project_path)
- expect(find('.breadcrumbs-sub-title')).to have_content(project.path)
+ expect(find('.breadcrumbs-sub-title')).to have_content('Details')
end
scenario 'the old project path redirects to the new path' do
update_username(new_username)
visit old_project_path
expect(current_path).to eq(new_project_path)
- expect(find('.breadcrumbs-sub-title')).to have_content(project.path)
+ expect(find('.breadcrumbs-sub-title')).to have_content('Details')
end
end
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/project/export_status.json b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
index d24a6f93f4b..81c8815caf6 100644
--- a/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
+++ b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
@@ -1,7 +1,9 @@
{
"type": "object",
"allOf": [
- { "$ref": "identity.json" },
+ {
+ "$ref": "identity.json"
+ },
{
"required": [
"export_status"
@@ -9,7 +11,12 @@
"properties": {
"export_status": {
"type": "string",
- "enum": ["none", "started", "finished"]
+ "enum": [
+ "none",
+ "started",
+ "finished",
+ "after_export_action"
+ ]
}
}
}
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index baf927a9acc..b77114a8152 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -50,6 +50,11 @@ describe PageLayoutHelper do
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
expect(helper.favicon).to eq 'favicon-blue.ico'
end
+
+ it 'has yellow favicon for canary' do
+ stub_env('CANARY', 'true')
+ expect(helper.favicon).to eq 'favicon-yellow.ico'
+ end
end
describe 'page_image' do
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 5477581c1b9..3d7ccf432be 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -35,14 +35,14 @@ describe('Api', () => {
});
describe('group', () => {
- it('fetches a group', (done) => {
+ it('fetches a group', done => {
const groupId = '123456';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`;
mock.onGet(expectedUrl).reply(200, {
name: 'test',
});
- Api.group(groupId, (response) => {
+ Api.group(groupId, response => {
expect(response.name).toBe('test');
done();
});
@@ -50,15 +50,17 @@ describe('Api', () => {
});
describe('groups', () => {
- it('fetches groups', (done) => {
+ it('fetches groups', done => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`;
- mock.onGet(expectedUrl).reply(200, [{
- name: 'test',
- }]);
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
- Api.groups(query, options, (response) => {
+ Api.groups(query, options, response => {
expect(response.length).toBe(1);
expect(response[0].name).toBe('test');
done();
@@ -67,14 +69,16 @@ describe('Api', () => {
});
describe('namespaces', () => {
- it('fetches namespaces', (done) => {
+ it('fetches namespaces', done => {
const query = 'dummy query';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`;
- mock.onGet(expectedUrl).reply(200, [{
- name: 'test',
- }]);
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
- Api.namespaces(query, (response) => {
+ Api.namespaces(query, response => {
expect(response.length).toBe(1);
expect(response[0].name).toBe('test');
done();
@@ -83,31 +87,35 @@ describe('Api', () => {
});
describe('projects', () => {
- it('fetches projects with membership when logged in', (done) => {
+ it('fetches projects with membership when logged in', done => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
window.gon.current_user_id = 1;
- mock.onGet(expectedUrl).reply(200, [{
- name: 'test',
- }]);
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
- Api.projects(query, options, (response) => {
+ Api.projects(query, options, response => {
expect(response.length).toBe(1);
expect(response[0].name).toBe('test');
done();
});
});
- it('fetches projects without membership when not logged in', (done) => {
+ it('fetches projects without membership when not logged in', done => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
- mock.onGet(expectedUrl).reply(200, [{
- name: 'test',
- }]);
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
- Api.projects(query, options, (response) => {
+ Api.projects(query, options, response => {
expect(response.length).toBe(1);
expect(response[0].name).toBe('test');
done();
@@ -115,8 +123,65 @@ describe('Api', () => {
});
});
+ describe('mergerequest', () => {
+ it('fetches a merge request', done => {
+ const projectPath = 'abc';
+ const mergeRequestId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`;
+ mock.onGet(expectedUrl).reply(200, {
+ title: 'test',
+ });
+
+ Api.mergeRequest(projectPath, mergeRequestId)
+ .then(({ data }) => {
+ expect(data.title).toBe('test');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('mergerequest changes', () => {
+ it('fetches the changes of a merge request', done => {
+ const projectPath = 'abc';
+ const mergeRequestId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`;
+ mock.onGet(expectedUrl).reply(200, {
+ title: 'test',
+ });
+
+ Api.mergeRequestChanges(projectPath, mergeRequestId)
+ .then(({ data }) => {
+ expect(data.title).toBe('test');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('mergerequest versions', () => {
+ it('fetches the versions of a merge request', done => {
+ const projectPath = 'abc';
+ const mergeRequestId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ id: 123,
+ },
+ ]);
+
+ Api.mergeRequestVersions(projectPath, mergeRequestId)
+ .then(({ data }) => {
+ expect(data.length).toBe(1);
+ expect(data[0].id).toBe(123);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('newLabel', () => {
- it('creates a new label', (done) => {
+ it('creates a new label', done => {
const namespace = 'some namespace';
const project = 'some project';
const labelData = { some: 'data' };
@@ -124,36 +189,42 @@ describe('Api', () => {
const expectedData = {
label: labelData,
};
- mock.onPost(expectedUrl).reply((config) => {
+ mock.onPost(expectedUrl).reply(config => {
expect(config.data).toBe(JSON.stringify(expectedData));
- return [200, {
- name: 'test',
- }];
+ return [
+ 200,
+ {
+ name: 'test',
+ },
+ ];
});
- Api.newLabel(namespace, project, labelData, (response) => {
+ Api.newLabel(namespace, project, labelData, response => {
expect(response.name).toBe('test');
done();
});
});
- it('creates a group label', (done) => {
+ it('creates a group label', done => {
const namespace = 'group/subgroup';
const labelData = { some: 'data' };
const expectedUrl = `${dummyUrlRoot}/groups/${namespace}/-/labels`;
const expectedData = {
label: labelData,
};
- mock.onPost(expectedUrl).reply((config) => {
+ mock.onPost(expectedUrl).reply(config => {
expect(config.data).toBe(JSON.stringify(expectedData));
- return [200, {
- name: 'test',
- }];
+ return [
+ 200,
+ {
+ name: 'test',
+ },
+ ];
});
- Api.newLabel(namespace, undefined, labelData, (response) => {
+ Api.newLabel(namespace, undefined, labelData, response => {
expect(response.name).toBe('test');
done();
});
@@ -161,15 +232,17 @@ describe('Api', () => {
});
describe('groupProjects', () => {
- it('fetches group projects', (done) => {
+ it('fetches group projects', done => {
const groupId = '123456';
const query = 'dummy query';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`;
- mock.onGet(expectedUrl).reply(200, [{
- name: 'test',
- }]);
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
- Api.groupProjects(groupId, query, (response) => {
+ Api.groupProjects(groupId, query, response => {
expect(response.length).toBe(1);
expect(response[0].name).toBe('test');
done();
@@ -178,13 +251,13 @@ describe('Api', () => {
});
describe('licenseText', () => {
- it('fetches a license text', (done) => {
+ it('fetches a license text', done => {
const licenseKey = "driver's license";
const data = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/licenses/${licenseKey}`;
mock.onGet(expectedUrl).reply(200, 'test');
- Api.licenseText(licenseKey, data, (response) => {
+ Api.licenseText(licenseKey, data, response => {
expect(response).toBe('test');
done();
});
@@ -192,12 +265,12 @@ describe('Api', () => {
});
describe('gitignoreText', () => {
- it('fetches a gitignore text', (done) => {
+ it('fetches a gitignore text', done => {
const gitignoreKey = 'ignore git';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitignores/${gitignoreKey}`;
mock.onGet(expectedUrl).reply(200, 'test');
- Api.gitignoreText(gitignoreKey, (response) => {
+ Api.gitignoreText(gitignoreKey, response => {
expect(response).toBe('test');
done();
});
@@ -205,12 +278,12 @@ describe('Api', () => {
});
describe('gitlabCiYml', () => {
- it('fetches a .gitlab-ci.yml', (done) => {
+ it('fetches a .gitlab-ci.yml', done => {
const gitlabCiYmlKey = 'Y CI ML';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitlab_ci_ymls/${gitlabCiYmlKey}`;
mock.onGet(expectedUrl).reply(200, 'test');
- Api.gitlabCiYml(gitlabCiYmlKey, (response) => {
+ Api.gitlabCiYml(gitlabCiYmlKey, response => {
expect(response).toBe('test');
done();
});
@@ -218,12 +291,12 @@ describe('Api', () => {
});
describe('dockerfileYml', () => {
- it('fetches a Dockerfile', (done) => {
+ it('fetches a Dockerfile', done => {
const dockerfileYmlKey = 'a giant whale';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/dockerfiles/${dockerfileYmlKey}`;
mock.onGet(expectedUrl).reply(200, 'test');
- Api.dockerfileYml(dockerfileYmlKey, (response) => {
+ Api.dockerfileYml(dockerfileYmlKey, response => {
expect(response).toBe('test');
done();
});
@@ -231,12 +304,14 @@ describe('Api', () => {
});
describe('issueTemplate', () => {
- it('fetches an issue template', (done) => {
+ it('fetches an issue template', done => {
const namespace = 'some namespace';
const project = 'some project';
const templateKey = ' template #%?.key ';
const templateType = 'template type';
- const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(templateKey)}`;
+ const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(
+ templateKey,
+ )}`;
mock.onGet(expectedUrl).reply(200, 'test');
Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => {
@@ -247,13 +322,15 @@ describe('Api', () => {
});
describe('users', () => {
- it('fetches users', (done) => {
+ it('fetches users', done => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`;
- mock.onGet(expectedUrl).reply(200, [{
- name: 'test',
- }]);
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
Api.users(query, options)
.then(({ data }) => {
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 0671facb285..81f1a97112f 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,7 +1,4 @@
/* global BoardService */
-/* eslint-disable comma-dangle, no-unused-vars, quote-props */
-import _ from 'underscore';
-
export const listObj = {
id: 300,
position: 0,
@@ -11,8 +8,8 @@ export const listObj = {
id: 5000,
title: 'Testing',
color: 'red',
- description: 'testing;'
- }
+ description: 'testing;',
+ },
};
export const listObjDuplicate = {
@@ -24,35 +21,37 @@ export const listObjDuplicate = {
id: listObj.label.id,
title: 'Testing',
color: 'red',
- description: 'testing;'
- }
+ description: 'testing;',
+ },
};
export const BoardsMockData = {
- 'GET': {
+ GET: {
'/test/-/boards/1/lists/300/issues?id=300&page=1&=': {
- issues: [{
- title: 'Testing',
- id: 1,
- iid: 1,
- confidential: false,
- labels: [],
- assignees: [],
- }],
- }
+ issues: [
+ {
+ title: 'Testing',
+ id: 1,
+ iid: 1,
+ confidential: false,
+ labels: [],
+ assignees: [],
+ },
+ ],
+ },
+ },
+ POST: {
+ '/test/-/boards/1/lists': listObj,
},
- 'POST': {
- '/test/-/boards/1/lists': listObj
+ PUT: {
+ '/test/issue-boards/board/1/lists{/id}': {},
},
- 'PUT': {
- '/test/issue-boards/board/1/lists{/id}': {}
+ DELETE: {
+ '/test/issue-boards/board/1/lists{/id}': {},
},
- 'DELETE': {
- '/test/issue-boards/board/1/lists{/id}': {}
- }
};
-export const boardsMockInterceptor = (config) => {
+export const boardsMockInterceptor = config => {
const body = BoardsMockData[config.method.toUpperCase()][config.url];
return [200, body];
};
diff --git a/spec/javascripts/droplab/constants_spec.js b/spec/javascripts/droplab/constants_spec.js
index b9d28db74cc..23b69defec6 100644
--- a/spec/javascripts/droplab/constants_spec.js
+++ b/spec/javascripts/droplab/constants_spec.js
@@ -1,39 +1,37 @@
-/* eslint-disable */
-
import * as constants from '~/droplab/constants';
-describe('constants', function () {
- describe('DATA_TRIGGER', function () {
+describe('constants', function() {
+ describe('DATA_TRIGGER', function() {
it('should be `data-dropdown-trigger`', function() {
expect(constants.DATA_TRIGGER).toBe('data-dropdown-trigger');
});
});
- describe('DATA_DROPDOWN', function () {
+ describe('DATA_DROPDOWN', function() {
it('should be `data-dropdown`', function() {
expect(constants.DATA_DROPDOWN).toBe('data-dropdown');
});
});
- describe('SELECTED_CLASS', function () {
+ describe('SELECTED_CLASS', function() {
it('should be `droplab-item-selected`', function() {
expect(constants.SELECTED_CLASS).toBe('droplab-item-selected');
});
});
- describe('ACTIVE_CLASS', function () {
+ describe('ACTIVE_CLASS', function() {
it('should be `droplab-item-active`', function() {
expect(constants.ACTIVE_CLASS).toBe('droplab-item-active');
});
});
- describe('TEMPLATE_REGEX', function () {
+ describe('TEMPLATE_REGEX', function() {
it('should be a handlebars templating syntax regex', function() {
expect(constants.TEMPLATE_REGEX).toEqual(/\{\{(.+?)\}\}/g);
});
});
- describe('IGNORE_CLASS', function () {
+ describe('IGNORE_CLASS', function() {
it('should be `droplab-item-ignore`', function() {
expect(constants.IGNORE_CLASS).toBe('droplab-item-ignore');
});
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
index b344b389241..e8865b04874 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -17,8 +17,6 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
end
before do
- # EE-specific start
- # EE specific end
project.add_master(admin)
sign_in(admin)
end
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index dc0a5bc275c..1cb20a1e7ff 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -81,13 +81,21 @@ describe('GfmAutoComplete', function () {
});
it('should quote if value contains any non-alphanumeric characters', () => {
- expect(beforeInsert(atwhoInstance, '~label-20')).toBe('~"label-20"');
+ expect(beforeInsert(atwhoInstance, '~label-20')).toBe('~"label\\-20"');
expect(beforeInsert(atwhoInstance, '~label 20')).toBe('~"label 20"');
});
it('should quote integer labels', () => {
expect(beforeInsert(atwhoInstance, '~1234')).toBe('~"1234"');
});
+
+ it('should escape Markdown emphasis characters, except in the first character', () => {
+ expect(beforeInsert(atwhoInstance, '@_group')).toEqual('@\\_group');
+ expect(beforeInsert(atwhoInstance, '~_bug')).toEqual('~\\_bug');
+ expect(beforeInsert(atwhoInstance, '~a `bug`')).toEqual('~"a \\`bug\\`"');
+ expect(beforeInsert(atwhoInstance, '~a ~bug')).toEqual('~"a \\~bug"');
+ expect(beforeInsert(atwhoInstance, '~a **bug')).toEqual('~"a \\*\\*bug"');
+ });
});
describe('DefaultOptions.matcher', function () {
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
index 2d386fe1da5..83f29d1b0c2 100644
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ b/spec/javascripts/helpers/vuex_action_helper.js
@@ -1,37 +1,71 @@
-/* eslint-disable */
-
/**
- * helper for testing action with expected mutations
+ * helper for testing action with expected mutations inspired in
* https://vuex.vuejs.org/en/testing.html
+ *
+ * @example
+ * testAction(
+ * actions.actionName, // action
+ * { }, // mocked response
+ * state, // state
+ * [
+ * { type: types.MUTATION}
+ * { type: types.MUTATION_1, payload: {}}
+ * ], // mutations
+ * [
+ * { type: 'actionName', payload: {}},
+ * { type: 'actionName1', payload: {}}
+ * ] //actions
+ * done,
+ * );
*/
-export default (action, payload, state, expectedMutations, done) => {
- let count = 0;
+export default (action, payload, state, expectedMutations, expectedActions, done) => {
+ let mutationsCount = 0;
+ let actionsCount = 0;
// mock commit
- const commit = (type, payload) => {
- const mutation = expectedMutations[count];
-
- try {
- expect(mutation.type).to.equal(type);
- if (payload) {
- expect(mutation.payload).to.deep.equal(payload);
- }
- } catch (error) {
- done(error);
+ const commit = (type, mutationPayload) => {
+ const mutation = expectedMutations[mutationsCount];
+
+ expect(mutation.type).toEqual(type);
+
+ if (mutation.payload) {
+ expect(mutation.payload).toEqual(mutationPayload);
}
- count++;
- if (count >= expectedMutations.length) {
+ mutationsCount += 1;
+ if (mutationsCount >= expectedMutations.length) {
+ done();
+ }
+ };
+
+ // mock dispatch
+ const dispatch = (type, actionPayload) => {
+ const actionExpected = expectedActions[actionsCount];
+
+ expect(actionExpected.type).toEqual(type);
+
+ if (actionExpected.payload) {
+ expect(actionExpected.payload).toEqual(actionPayload);
+ }
+
+ actionsCount += 1;
+ if (actionsCount >= expectedActions.length) {
done();
}
};
// call the action with mocked store and arguments
- action({ commit, state }, payload);
+ action({ commit, state, dispatch }, payload);
// check if no mutations should have been dispatched
if (expectedMutations.length === 0) {
- expect(count).to.equal(0);
+ expect(mutationsCount).toEqual(0);
+ done();
+ }
+
+ // check if no mutations should have been dispatched
+ if (expectedActions.length === 0) {
+ expect(actionsCount).toEqual(0);
done();
}
};
diff --git a/spec/javascripts/ide/components/changed_file_icon_spec.js b/spec/javascripts/ide/components/changed_file_icon_spec.js
index 987aea7befc..541864e912e 100644
--- a/spec/javascripts/ide/components/changed_file_icon_spec.js
+++ b/spec/javascripts/ide/components/changed_file_icon_spec.js
@@ -11,6 +11,7 @@ describe('IDE changed file icon', () => {
vm = createComponent(component, {
file: {
tempFile: false,
+ changed: true,
},
});
});
@@ -20,7 +21,7 @@ describe('IDE changed file icon', () => {
});
describe('changedIcon', () => {
- it('equals file-modified when not a temp file', () => {
+ it('equals file-modified when not a temp file and has changes', () => {
expect(vm.changedIcon).toBe('file-modified');
});
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index ae657e8c881..9d3fa1280f4 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -89,6 +89,20 @@ describe('RepoEditor', () => {
done();
});
});
+
+ it('calls createDiffInstance when viewer is a merge request diff', done => {
+ vm.$store.state.viewer = 'mrdiff';
+
+ spyOn(vm.editor, 'createDiffInstance');
+
+ vm.createEditorInstance();
+
+ vm.$nextTick(() => {
+ expect(vm.editor.createDiffInstance).toHaveBeenCalled();
+
+ done();
+ });
+ });
});
describe('setupEditor', () => {
@@ -134,4 +148,48 @@ describe('RepoEditor', () => {
});
});
});
+
+ describe('setup editor for merge request viewing', () => {
+ beforeEach(done => {
+ // Resetting as the main test setup has already done it
+ vm.$destroy();
+ resetStore(vm.$store);
+ Editor.editorInstance.modelManager.dispose();
+
+ const f = {
+ ...file(),
+ active: true,
+ tempFile: true,
+ html: 'testing',
+ mrChange: { diff: 'ABC' },
+ baseRaw: 'testing',
+ content: 'test',
+ };
+ const RepoEditor = Vue.extend(repoEditor);
+ vm = createComponentWithStore(RepoEditor, store, {
+ file: f,
+ });
+
+ vm.$store.state.openFiles.push(f);
+ vm.$store.state.entries[f.path] = f;
+
+ vm.$store.state.viewer = 'mrdiff';
+
+ vm.monaco = true;
+
+ vm.$mount();
+
+ monacoLoader(['vs/editor/editor.main'], () => {
+ setTimeout(done, 0);
+ });
+ });
+
+ it('attaches merge request model to editor when merge request diff', () => {
+ spyOn(vm.editor, 'attachMergeRequestModel').and.callThrough();
+
+ vm.setupEditor();
+
+ expect(vm.editor.attachMergeRequestModel).toHaveBeenCalledWith(vm.model);
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/repo_tabs_spec.js b/spec/javascripts/ide/components/repo_tabs_spec.js
index 1cd2e362f50..cb785ba2cd3 100644
--- a/spec/javascripts/ide/components/repo_tabs_spec.js
+++ b/spec/javascripts/ide/components/repo_tabs_spec.js
@@ -18,6 +18,7 @@ describe('RepoTabs', () => {
viewer: 'editor',
hasChanges: false,
activeFile: file('activeFile'),
+ hasMergeRequest: false,
});
openedFiles[0].active = true;
@@ -58,6 +59,7 @@ describe('RepoTabs', () => {
viewer: 'editor',
hasChanges: false,
activeFile: file('activeFile'),
+ hasMergeRequest: false,
},
'#test-app',
);
diff --git a/spec/javascripts/ide/lib/common/model_spec.js b/spec/javascripts/ide/lib/common/model_spec.js
index f6979e32cb3..8fc2fccb64c 100644
--- a/spec/javascripts/ide/lib/common/model_spec.js
+++ b/spec/javascripts/ide/lib/common/model_spec.js
@@ -11,7 +11,10 @@ describe('Multi-file editor library model', () => {
spyOn(eventHub, '$on').and.callThrough();
monacoLoader(['vs/editor/editor.main'], () => {
- model = new Model(monaco, file('path'));
+ const f = file('path');
+ f.mrChange = { diff: 'ABC' };
+ f.baseRaw = 'test';
+ model = new Model(monaco, f);
done();
});
@@ -21,9 +24,10 @@ describe('Multi-file editor library model', () => {
model.dispose();
});
- it('creates original model & new model', () => {
+ it('creates original model & base model & new model', () => {
expect(model.originalModel).not.toBeNull();
expect(model.model).not.toBeNull();
+ expect(model.baseModel).not.toBeNull();
});
it('adds eventHub listener', () => {
@@ -51,6 +55,12 @@ describe('Multi-file editor library model', () => {
});
});
+ describe('getBaseModel', () => {
+ it('returns base model', () => {
+ expect(model.getBaseModel()).toBe(model.baseModel);
+ });
+ });
+
describe('setValue', () => {
it('updates models value', () => {
model.setValue('testing 123');
diff --git a/spec/javascripts/ide/lib/editor_spec.js b/spec/javascripts/ide/lib/editor_spec.js
index 2ccd87de1a7..ec56ebc0341 100644
--- a/spec/javascripts/ide/lib/editor_spec.js
+++ b/spec/javascripts/ide/lib/editor_spec.js
@@ -143,6 +143,31 @@ describe('Multi-file editor library', () => {
});
});
+ describe('attachMergeRequestModel', () => {
+ let model;
+
+ beforeEach(() => {
+ instance.createDiffInstance(document.createElement('div'));
+
+ const f = file();
+ f.mrChanges = { diff: 'ABC' };
+ f.baseRaw = 'testing';
+
+ model = instance.createModel(f);
+ });
+
+ it('sets original & modified', () => {
+ spyOn(instance.instance, 'setModel');
+
+ instance.attachMergeRequestModel(model);
+
+ expect(instance.instance.setModel).toHaveBeenCalledWith({
+ original: model.getBaseModel(),
+ modified: model.getModel(),
+ });
+ });
+ });
+
describe('clearEditor', () => {
it('resets the editor model', () => {
instance.createInstance(document.createElement('div'));
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index eb8933b2b3f..479ed7ce49e 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -5,7 +5,7 @@ import router from '~/ide/ide_router';
import eventHub from '~/ide/eventhub';
import { file, resetStore } from '../../helpers';
-describe('Multi-file store file actions', () => {
+describe('IDE store file actions', () => {
beforeEach(() => {
spyOn(router, 'push');
});
@@ -205,7 +205,7 @@ describe('Multi-file store file actions', () => {
it('calls the service', done => {
store
- .dispatch('getFileData', localFile)
+ .dispatch('getFileData', { path: localFile.path })
.then(() => {
expect(service.getFileData).toHaveBeenCalledWith('getFileDataURL');
@@ -216,7 +216,7 @@ describe('Multi-file store file actions', () => {
it('sets the file data', done => {
store
- .dispatch('getFileData', localFile)
+ .dispatch('getFileData', { path: localFile.path })
.then(() => {
expect(localFile.blamePath).toBe('blame_path');
@@ -227,7 +227,7 @@ describe('Multi-file store file actions', () => {
it('sets document title', done => {
store
- .dispatch('getFileData', localFile)
+ .dispatch('getFileData', { path: localFile.path })
.then(() => {
expect(document.title).toBe('testing getFileData');
@@ -238,7 +238,7 @@ describe('Multi-file store file actions', () => {
it('sets the file as active', done => {
store
- .dispatch('getFileData', localFile)
+ .dispatch('getFileData', { path: localFile.path })
.then(() => {
expect(localFile.active).toBeTruthy();
@@ -247,9 +247,20 @@ describe('Multi-file store file actions', () => {
.catch(done.fail);
});
+ it('sets the file not as active if we pass makeFileActive false', done => {
+ store
+ .dispatch('getFileData', { path: localFile.path, makeFileActive: false })
+ .then(() => {
+ expect(localFile.active).toBeFalsy();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
it('adds the file to open files', done => {
store
- .dispatch('getFileData', localFile)
+ .dispatch('getFileData', { path: localFile.path })
.then(() => {
expect(store.state.openFiles.length).toBe(1);
expect(store.state.openFiles[0].name).toBe(localFile.name);
@@ -272,7 +283,7 @@ describe('Multi-file store file actions', () => {
it('calls getRawFileData service method', done => {
store
- .dispatch('getRawFileData', tmpFile)
+ .dispatch('getRawFileData', { path: tmpFile.path })
.then(() => {
expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
@@ -283,7 +294,7 @@ describe('Multi-file store file actions', () => {
it('updates file raw data', done => {
store
- .dispatch('getRawFileData', tmpFile)
+ .dispatch('getRawFileData', { path: tmpFile.path })
.then(() => {
expect(tmpFile.raw).toBe('raw');
@@ -291,6 +302,22 @@ describe('Multi-file store file actions', () => {
})
.catch(done.fail);
});
+
+ it('calls also getBaseRawFileData service method', done => {
+ spyOn(service, 'getBaseRawFileData').and.returnValue(Promise.resolve('baseraw'));
+
+ tmpFile.mrChange = { new_file: false };
+
+ store
+ .dispatch('getRawFileData', { path: tmpFile.path, baseSha: 'SHA' })
+ .then(() => {
+ expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA');
+ expect(tmpFile.baseRaw).toBe('baseraw');
+
+ done();
+ })
+ .catch(done.fail);
+ });
});
describe('changeFileContent', () => {
diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js
new file mode 100644
index 00000000000..b4ec4a0b173
--- /dev/null
+++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js
@@ -0,0 +1,110 @@
+import store from '~/ide/stores';
+import service from '~/ide/services';
+import { resetStore } from '../../helpers';
+
+describe('IDE store merge request actions', () => {
+ beforeEach(() => {
+ store.state.projects.abcproject = {
+ mergeRequests: {},
+ };
+ });
+
+ afterEach(() => {
+ resetStore(store);
+ });
+
+ describe('getMergeRequestData', () => {
+ beforeEach(() => {
+ spyOn(service, 'getProjectMergeRequestData').and.returnValue(
+ Promise.resolve({ data: { title: 'mergerequest' } }),
+ );
+ });
+
+ it('calls getProjectMergeRequestData service method', done => {
+ store
+ .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
+ .then(() => {
+ expect(service.getProjectMergeRequestData).toHaveBeenCalledWith('abcproject', 1);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('sets the Merge Request Object', done => {
+ store
+ .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
+ .then(() => {
+ expect(store.state.projects.abcproject.mergeRequests['1'].title).toBe('mergerequest');
+ expect(store.state.currentMergeRequestId).toBe(1);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('getMergeRequestChanges', () => {
+ beforeEach(() => {
+ spyOn(service, 'getProjectMergeRequestChanges').and.returnValue(
+ Promise.resolve({ data: { title: 'mergerequest' } }),
+ );
+
+ store.state.projects.abcproject.mergeRequests['1'] = { changes: [] };
+ });
+
+ it('calls getProjectMergeRequestChanges service method', done => {
+ store
+ .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
+ .then(() => {
+ expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith('abcproject', 1);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('sets the Merge Request Changes Object', done => {
+ store
+ .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
+ .then(() => {
+ expect(store.state.projects.abcproject.mergeRequests['1'].changes.title).toBe(
+ 'mergerequest',
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('getMergeRequestVersions', () => {
+ beforeEach(() => {
+ spyOn(service, 'getProjectMergeRequestVersions').and.returnValue(
+ Promise.resolve({ data: [{ id: 789 }] }),
+ );
+
+ store.state.projects.abcproject.mergeRequests['1'] = { versions: [] };
+ });
+
+ it('calls getProjectMergeRequestVersions service method', done => {
+ store
+ .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
+ .then(() => {
+ expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith('abcproject', 1);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('sets the Merge Request Versions Object', done => {
+ store
+ .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
+ .then(() => {
+ expect(store.state.projects.abcproject.mergeRequests['1'].versions.length).toBe(1);
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js
index 381f038067b..e0ef57a3966 100644
--- a/spec/javascripts/ide/stores/actions/tree_spec.js
+++ b/spec/javascripts/ide/stores/actions/tree_spec.js
@@ -68,9 +68,7 @@ describe('Multi-file store tree actions', () => {
expect(projectTree.tree[0].tree[1].name).toBe('fileinfolder.js');
expect(projectTree.tree[1].type).toBe('blob');
expect(projectTree.tree[0].tree[0].tree[0].type).toBe('blob');
- expect(projectTree.tree[0].tree[0].tree[0].name).toBe(
- 'fileinsubfolder.js',
- );
+ expect(projectTree.tree[0].tree[0].tree[0].name).toBe('fileinsubfolder.js');
done();
})
@@ -132,9 +130,7 @@ describe('Multi-file store tree actions', () => {
store
.dispatch('getLastCommitData', projectTree)
.then(() => {
- expect(service.getTreeLastCommit).toHaveBeenCalledWith(
- 'lastcommitpath',
- );
+ expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath');
done();
})
@@ -160,9 +156,7 @@ describe('Multi-file store tree actions', () => {
.dispatch('getLastCommitData', projectTree)
.then(Vue.nextTick)
.then(() => {
- expect(projectTree.tree[0].lastCommit.message).not.toBe(
- 'commit message',
- );
+ expect(projectTree.tree[0].lastCommit.message).not.toBe('commit message');
done();
})
diff --git a/spec/javascripts/ide/stores/getters_spec.js b/spec/javascripts/ide/stores/getters_spec.js
index a613f3a21cc..33733b97dff 100644
--- a/spec/javascripts/ide/stores/getters_spec.js
+++ b/spec/javascripts/ide/stores/getters_spec.js
@@ -2,7 +2,7 @@ import * as getters from '~/ide/stores/getters';
import state from '~/ide/stores/state';
import { file } from '../helpers';
-describe('Multi-file store getters', () => {
+describe('IDE store getters', () => {
let localState;
beforeEach(() => {
@@ -52,4 +52,24 @@ describe('Multi-file store getters', () => {
expect(modifiedFiles[0].name).toBe('added');
});
});
+
+ describe('currentMergeRequest', () => {
+ it('returns Current Merge Request', () => {
+ localState.currentProjectId = 'abcproject';
+ localState.currentMergeRequestId = 1;
+ localState.projects.abcproject = {
+ mergeRequests: {
+ 1: { mergeId: 1 },
+ },
+ };
+
+ expect(getters.currentMergeRequest(localState).mergeId).toBe(1);
+ });
+
+ it('returns null if no active Merge Request was found', () => {
+ localState.currentProjectId = 'otherproject';
+
+ expect(getters.currentMergeRequest(localState)).toBeNull();
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/mutations/file_spec.js b/spec/javascripts/ide/stores/mutations/file_spec.js
index 4f9e00b8543..88285ee409f 100644
--- a/spec/javascripts/ide/stores/mutations/file_spec.js
+++ b/spec/javascripts/ide/stores/mutations/file_spec.js
@@ -2,7 +2,7 @@ import mutations from '~/ide/stores/mutations/file';
import state from '~/ide/stores/state';
import { file } from '../../helpers';
-describe('Multi-file store file mutations', () => {
+describe('IDE store file mutations', () => {
let localState;
let localFile;
@@ -77,6 +77,8 @@ describe('Multi-file store file mutations', () => {
expect(localFile.rawPath).toBe('raw');
expect(localFile.binary).toBeTruthy();
expect(localFile.renderError).toBe('render_error');
+ expect(localFile.raw).toBeNull();
+ expect(localFile.baseRaw).toBeNull();
});
});
@@ -91,6 +93,17 @@ describe('Multi-file store file mutations', () => {
});
});
+ describe('SET_FILE_BASE_RAW_DATA', () => {
+ it('sets raw data from base branch', () => {
+ mutations.SET_FILE_BASE_RAW_DATA(localState, {
+ file: localFile,
+ baseRaw: 'testing',
+ });
+
+ expect(localFile.baseRaw).toBe('testing');
+ });
+ });
+
describe('UPDATE_FILE_CONTENT', () => {
beforeEach(() => {
localFile.raw = 'test';
@@ -127,6 +140,17 @@ describe('Multi-file store file mutations', () => {
});
});
+ describe('SET_FILE_MERGE_REQUEST_CHANGE', () => {
+ it('sets file mr change', () => {
+ mutations.SET_FILE_MERGE_REQUEST_CHANGE(localState, {
+ file: localFile,
+ mrChange: { diff: 'ABC' },
+ });
+
+ expect(localFile.mrChange.diff).toBe('ABC');
+ });
+ });
+
describe('DISCARD_FILE_CHANGES', () => {
beforeEach(() => {
localFile.content = 'test';
diff --git a/spec/javascripts/ide/stores/mutations/merge_request_spec.js b/spec/javascripts/ide/stores/mutations/merge_request_spec.js
new file mode 100644
index 00000000000..f724bf464f5
--- /dev/null
+++ b/spec/javascripts/ide/stores/mutations/merge_request_spec.js
@@ -0,0 +1,65 @@
+import mutations from '~/ide/stores/mutations/merge_request';
+import state from '~/ide/stores/state';
+
+describe('IDE store merge request mutations', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ localState.projects = { abcproject: { mergeRequests: {} } };
+
+ mutations.SET_MERGE_REQUEST(localState, {
+ projectPath: 'abcproject',
+ mergeRequestId: 1,
+ mergeRequest: {
+ title: 'mr',
+ },
+ });
+ });
+
+ describe('SET_CURRENT_MERGE_REQUEST', () => {
+ it('sets current merge request', () => {
+ mutations.SET_CURRENT_MERGE_REQUEST(localState, 2);
+
+ expect(localState.currentMergeRequestId).toBe(2);
+ });
+ });
+
+ describe('SET_MERGE_REQUEST', () => {
+ it('setsmerge request data', () => {
+ const newMr = localState.projects.abcproject.mergeRequests[1];
+
+ expect(newMr.title).toBe('mr');
+ expect(newMr.active).toBeTruthy();
+ });
+ });
+
+ describe('SET_MERGE_REQUEST_CHANGES', () => {
+ it('sets merge request changes', () => {
+ mutations.SET_MERGE_REQUEST_CHANGES(localState, {
+ projectPath: 'abcproject',
+ mergeRequestId: 1,
+ changes: {
+ diff: 'abc',
+ },
+ });
+
+ const newMr = localState.projects.abcproject.mergeRequests[1];
+ expect(newMr.changes.diff).toBe('abc');
+ });
+ });
+
+ describe('SET_MERGE_REQUEST_VERSIONS', () => {
+ it('sets merge request versions', () => {
+ mutations.SET_MERGE_REQUEST_VERSIONS(localState, {
+ projectPath: 'abcproject',
+ mergeRequestId: 1,
+ versions: [{ id: 123 }],
+ });
+
+ const newMr = localState.projects.abcproject.mergeRequests[1];
+ expect(newMr.versions.length).toBe(1);
+ expect(newMr.versions[0].id).toBe(123);
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 43589d54be4..25ca8eb6c0b 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -115,6 +115,10 @@ export default {
commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
},
},
+ metadata: {
+ timeout_human_readable: '1m 40s',
+ timeout_source: 'runner',
+ },
merge_request: {
iid: 2,
path: '/root/ci-mock/merge_requests/2',
diff --git a/spec/javascripts/jobs/sidebar_detail_row_spec.js b/spec/javascripts/jobs/sidebar_detail_row_spec.js
index 3ac65709c4a..e6bfb0c4adc 100644
--- a/spec/javascripts/jobs/sidebar_detail_row_spec.js
+++ b/spec/javascripts/jobs/sidebar_detail_row_spec.js
@@ -37,4 +37,25 @@ describe('Sidebar detail row', () => {
vm.$el.textContent.replace(/\s+/g, ' ').trim(),
).toEqual('this is the title: this is the value');
});
+
+ describe('when helpUrl not provided', () => {
+ it('should not render help', () => {
+ expect(vm.$el.querySelector('.help-button')).toBeNull();
+ });
+ });
+
+ describe('when helpUrl provided', () => {
+ beforeEach(() => {
+ vm = new SidebarDetailRow({
+ propsData: {
+ helpUrl: 'help url',
+ value: 'foo',
+ },
+ }).$mount();
+ });
+
+ it('should render help', () => {
+ expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url');
+ });
+ });
});
diff --git a/spec/javascripts/jobs/sidebar_details_block_spec.js b/spec/javascripts/jobs/sidebar_details_block_spec.js
index 95532ef5382..602dae514b1 100644
--- a/spec/javascripts/jobs/sidebar_details_block_spec.js
+++ b/spec/javascripts/jobs/sidebar_details_block_spec.js
@@ -96,6 +96,12 @@ describe('Sidebar details block', () => {
).toEqual('Runner: #1');
});
+ it('should render timeout information', () => {
+ expect(
+ trimWhitespace(vm.$el.querySelector('.js-job-timeout')),
+ ).toEqual('Timeout: 1m 40s (from runner)');
+ });
+
it('should render coverage', () => {
expect(
trimWhitespace(vm.$el.querySelector('.js-job-coverage')),
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index 19504e4f7c8..cda550760fe 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -25,26 +25,34 @@ describe('issue_discussion component', () => {
});
it('should render user avatar', () => {
- expect(vm.$el.querySelector('.user-avatar-link')).toBeDefined();
+ expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull();
});
it('should render discussion header', () => {
- expect(vm.$el.querySelector('.discussion-header')).toBeDefined();
+ expect(vm.$el.querySelector('.discussion-header')).not.toBeNull();
expect(vm.$el.querySelector('.notes').children.length).toEqual(discussionMock.notes.length);
});
describe('actions', () => {
it('should render reply button', () => {
- expect(vm.$el.querySelector('.js-vue-discussion-reply').textContent.trim()).toEqual('Reply...');
+ expect(vm.$el.querySelector('.js-vue-discussion-reply').textContent.trim()).toEqual(
+ 'Reply...',
+ );
});
- it('should toggle reply form', (done) => {
+ it('should toggle reply form', done => {
vm.$el.querySelector('.js-vue-discussion-reply').click();
Vue.nextTick(() => {
- expect(vm.$refs.noteForm).toBeDefined();
+ expect(vm.$refs.noteForm).not.toBeNull();
expect(vm.isReplying).toEqual(true);
done();
});
});
+
+ it('does not render jump to discussion button', () => {
+ expect(
+ vm.$el.querySelector('*[data-original-title="Jump to next unresolved discussion"]'),
+ ).toBeNull();
+ });
});
});
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 5be13ed0dfe..2d88cee61f1 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -1,4 +1,3 @@
-/* eslint-disable */
export const notesDataMock = {
discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
lastFetchedAt: 1501862675,
@@ -43,7 +42,8 @@ export const noteableDataMock = {
milestone: null,
milestone_id: null,
moved_to_id: null,
- preview_note_path: '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
+ preview_note_path:
+ '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
project_id: 2,
state: 'opened',
time_estimate: 0,
@@ -60,465 +60,504 @@ export const individualNote = {
expanded: true,
id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
individual_note: true,
- notes: [{
- id: 1390,
- attachment: {
- url: null,
- filename: null,
- image: false,
- },
- author: {
- id: 1,
- name: 'Root',
- username: 'root',
- state: 'active',
- avatar_url: 'test',
- path: '/root',
+ notes: [
+ {
+ id: 1390,
+ attachment: {
+ url: null,
+ filename: null,
+ image: false,
+ },
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: 'test',
+ path: '/root',
+ },
+ created_at: '2017-08-01T17: 09: 33.762Z',
+ updated_at: '2017-08-01T17: 09: 33.762Z',
+ system: false,
+ noteable_id: 98,
+ noteable_type: 'Issue',
+ type: null,
+ human_access: 'Owner',
+ note: 'sdfdsaf',
+ note_html: "<p dir='auto'>sdfdsaf</p>",
+ current_user: { can_edit: true },
+ discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
+ emoji_awardable: true,
+ award_emoji: [
+ { name: 'baseball', user: { id: 1, name: 'Root', username: 'root' } },
+ { name: 'art', user: { id: 1, name: 'Root', username: 'root' } },
+ ],
+ toggle_award_path: '/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390&user_id=1',
+ path: '/gitlab-org/gitlab-ce/notes/1390',
},
- created_at: '2017-08-01T17: 09: 33.762Z',
- updated_at: '2017-08-01T17: 09: 33.762Z',
- system: false,
- noteable_id: 98,
- noteable_type: 'Issue',
- type: null,
- human_access: 'Owner',
- note: 'sdfdsaf',
- note_html: '<p dir=\'auto\'>sdfdsaf</p>',
- current_user: { can_edit: true },
- discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
- emoji_awardable: true,
- award_emoji: [
- { name: 'baseball', user: { id: 1, name: 'Root', username: 'root' } },
- { name: 'art', user: { id: 1, name: 'Root', username: 'root' } },
- ],
- toggle_award_path: '/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji',
- report_abuse_path: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390&user_id=1',
- path: '/gitlab-org/gitlab-ce/notes/1390',
- }],
+ ],
reply_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
};
export const note = {
- "id": 546,
- "attachment": {
- "url": null,
- "filename": null,
- "image": false
+ id: 546,
+ attachment: {
+ url: null,
+ filename: null,
+ image: false,
},
- "author": {
- "id": 1,
- "name": "Administrator",
- "username": "root",
- "state": "active",
- "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
- "path": "/root"
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
},
- "created_at": "2017-08-10T15:24:03.087Z",
- "updated_at": "2017-08-10T15:24:03.087Z",
- "system": false,
- "noteable_id": 67,
- "noteable_type": "Issue",
- "noteable_iid": 7,
- "type": null,
- "human_access": "Owner",
- "note": "Vel id placeat reprehenderit sit numquam.",
- "note_html": "<p dir=\"auto\">Vel id placeat reprehenderit sit numquam.</p>",
- "current_user": {
- "can_edit": true
+ created_at: '2017-08-10T15:24:03.087Z',
+ updated_at: '2017-08-10T15:24:03.087Z',
+ system: false,
+ noteable_id: 67,
+ noteable_type: 'Issue',
+ noteable_iid: 7,
+ type: null,
+ human_access: 'Owner',
+ note: 'Vel id placeat reprehenderit sit numquam.',
+ note_html: '<p dir="auto">Vel id placeat reprehenderit sit numquam.</p>',
+ current_user: {
+ can_edit: true,
},
- "discussion_id": "d3842a451b7f3d9a5dfce329515127b2d29a4cd0",
- "emoji_awardable": true,
- "award_emoji": [{
- "name": "baseball",
- "user": {
- "id": 1,
- "name": "Administrator",
- "username": "root"
- }
- }, {
- "name": "bath_tone3",
- "user": {
- "id": 1,
- "name": "Administrator",
- "username": "root"
- }
- }],
- "toggle_award_path": "/gitlab-org/gitlab-ce/notes/546/toggle_award_emoji",
- "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_546&user_id=1",
- "path": "/gitlab-org/gitlab-ce/notes/546"
- }
+ discussion_id: 'd3842a451b7f3d9a5dfce329515127b2d29a4cd0',
+ emoji_awardable: true,
+ award_emoji: [
+ {
+ name: 'baseball',
+ user: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ },
+ },
+ {
+ name: 'bath_tone3',
+ user: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ },
+ },
+ ],
+ toggle_award_path: '/gitlab-org/gitlab-ce/notes/546/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_546&user_id=1',
+ path: '/gitlab-org/gitlab-ce/notes/546',
+};
export const discussionMock = {
id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
reply_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
expanded: true,
- notes: [{
- id: 1395,
- attachment: {
- url: null,
- filename: null,
- image: false,
- },
- author: {
- id: 1,
- name: 'Root',
- username: 'root',
- state: 'active',
- avatar_url: null,
- path: '/root',
- },
- created_at: '2017-08-02T10:51:58.559Z',
- updated_at: '2017-08-02T10:51:58.559Z',
- system: false,
- noteable_id: 98,
- noteable_type: 'Issue',
- type: 'DiscussionNote',
- human_access: 'Owner',
- note: 'THIS IS A DICUSSSION!',
- note_html: '<p dir=\'auto\'>THIS IS A DICUSSSION!</p>',
- current_user: {
- can_edit: true,
- },
- discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
- emoji_awardable: true,
- award_emoji: [],
- toggle_award_path: '/gitlab-org/gitlab-ce/notes/1395/toggle_award_emoji',
- report_abuse_path: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1395&user_id=1',
- path: '/gitlab-org/gitlab-ce/notes/1395',
- }, {
- id: 1396,
- attachment: {
- url: null,
- filename: null,
- image: false,
- },
- author: {
- id: 1,
- name: 'Root',
- username: 'root',
- state: 'active',
- avatar_url: null,
- path: '/root',
- },
- created_at: '2017-08-02T10:56:50.980Z',
- updated_at: '2017-08-03T14:19:35.691Z',
- system: false,
- noteable_id: 98,
- noteable_type: 'Issue',
- type: 'DiscussionNote',
- human_access: 'Owner',
- note: 'sadfasdsdgdsf',
- note_html: '<p dir=\'auto\'>sadfasdsdgdsf</p>',
- last_edited_at: '2017-08-03T14:19:35.691Z',
- last_edited_by: {
- id: 1,
- name: 'Root',
- username: 'root',
- state: 'active',
- avatar_url: null,
- path: '/root',
- },
- current_user: {
- can_edit: true,
+ notes: [
+ {
+ id: 1395,
+ attachment: {
+ url: null,
+ filename: null,
+ image: false,
+ },
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
+ },
+ created_at: '2017-08-02T10:51:58.559Z',
+ updated_at: '2017-08-02T10:51:58.559Z',
+ system: false,
+ noteable_id: 98,
+ noteable_type: 'Issue',
+ type: 'DiscussionNote',
+ human_access: 'Owner',
+ note: 'THIS IS A DICUSSSION!',
+ note_html: "<p dir='auto'>THIS IS A DICUSSSION!</p>",
+ current_user: {
+ can_edit: true,
+ },
+ discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-ce/notes/1395/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1395&user_id=1',
+ path: '/gitlab-org/gitlab-ce/notes/1395',
},
- discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
- emoji_awardable: true,
- award_emoji: [],
- toggle_award_path: '/gitlab-org/gitlab-ce/notes/1396/toggle_award_emoji',
- report_abuse_path: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1396&user_id=1',
- path: '/gitlab-org/gitlab-ce/notes/1396',
- }, {
- id: 1437,
- attachment: {
- url: null,
- filename: null,
- image: false,
+ {
+ id: 1396,
+ attachment: {
+ url: null,
+ filename: null,
+ image: false,
+ },
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
+ },
+ created_at: '2017-08-02T10:56:50.980Z',
+ updated_at: '2017-08-03T14:19:35.691Z',
+ system: false,
+ noteable_id: 98,
+ noteable_type: 'Issue',
+ type: 'DiscussionNote',
+ human_access: 'Owner',
+ note: 'sadfasdsdgdsf',
+ note_html: "<p dir='auto'>sadfasdsdgdsf</p>",
+ last_edited_at: '2017-08-03T14:19:35.691Z',
+ last_edited_by: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
+ },
+ current_user: {
+ can_edit: true,
+ },
+ discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-ce/notes/1396/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1396&user_id=1',
+ path: '/gitlab-org/gitlab-ce/notes/1396',
},
- author: {
- id: 1,
- name: 'Root',
- username: 'root',
- state: 'active',
- avatar_url: null,
- path: '/root',
+ {
+ id: 1437,
+ attachment: {
+ url: null,
+ filename: null,
+ image: false,
+ },
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
+ },
+ created_at: '2017-08-03T18:11:18.780Z',
+ updated_at: '2017-08-04T09:52:31.062Z',
+ system: false,
+ noteable_id: 98,
+ noteable_type: 'Issue',
+ type: 'DiscussionNote',
+ human_access: 'Owner',
+ note: 'adsfasf Should disappear',
+ note_html: "<p dir='auto'>adsfasf Should disappear</p>",
+ last_edited_at: '2017-08-04T09:52:31.062Z',
+ last_edited_by: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
+ },
+ current_user: {
+ can_edit: true,
+ },
+ discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-ce/notes/1437/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1437&user_id=1',
+ path: '/gitlab-org/gitlab-ce/notes/1437',
},
- created_at: '2017-08-03T18:11:18.780Z',
- updated_at: '2017-08-04T09:52:31.062Z',
- system: false,
- noteable_id: 98,
- noteable_type: 'Issue',
- type: 'DiscussionNote',
- human_access: 'Owner',
- note: 'adsfasf Should disappear',
- note_html: '<p dir=\'auto\'>adsfasf Should disappear</p>',
- last_edited_at: '2017-08-04T09:52:31.062Z',
- last_edited_by: {
+ ],
+ individual_note: false,
+};
+
+export const loggedOutnoteableData = {
+ id: 98,
+ iid: 26,
+ author_id: 1,
+ description: '',
+ lock_version: 1,
+ milestone_id: null,
+ state: 'opened',
+ title: 'asdsa',
+ updated_by_id: 1,
+ created_at: '2017-02-07T10:11:18.395Z',
+ updated_at: '2017-08-08T10:22:51.564Z',
+ time_estimate: 0,
+ total_time_spent: 0,
+ human_time_estimate: null,
+ human_total_time_spent: null,
+ milestone: null,
+ labels: [],
+ branch_name: null,
+ confidential: false,
+ assignees: [
+ {
id: 1,
name: 'Root',
username: 'root',
state: 'active',
avatar_url: null,
- path: '/root',
+ web_url: 'http://localhost:3000/root',
},
- current_user: {
- can_edit: true,
- },
- discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
- emoji_awardable: true,
- award_emoji: [],
- toggle_award_path: '/gitlab-org/gitlab-ce/notes/1437/toggle_award_emoji',
- report_abuse_path: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1437&user_id=1',
- path: '/gitlab-org/gitlab-ce/notes/1437',
- }],
- individual_note: false,
-};
-
-export const loggedOutnoteableData = {
- "id": 98,
- "iid": 26,
- "author_id": 1,
- "description": "",
- "lock_version": 1,
- "milestone_id": null,
- "state": "opened",
- "title": "asdsa",
- "updated_by_id": 1,
- "created_at": "2017-02-07T10:11:18.395Z",
- "updated_at": "2017-08-08T10:22:51.564Z",
- "time_estimate": 0,
- "total_time_spent": 0,
- "human_time_estimate": null,
- "human_total_time_spent": null,
- "milestone": null,
- "labels": [],
- "branch_name": null,
- "confidential": false,
- "assignees": [{
- "id": 1,
- "name": "Root",
- "username": "root",
- "state": "active",
- "avatar_url": null,
- "web_url": "http://localhost:3000/root"
- }],
- "due_date": null,
- "moved_to_id": null,
- "project_id": 2,
- "web_url": "/gitlab-org/gitlab-ce/issues/26",
- "current_user": {
- "can_create_note": false,
- "can_update": false
+ ],
+ due_date: null,
+ moved_to_id: null,
+ project_id: 2,
+ web_url: '/gitlab-org/gitlab-ce/issues/26',
+ current_user: {
+ can_create_note: false,
+ can_update: false,
},
- "create_note_path": "/gitlab-org/gitlab-ce/notes?target_id=98&target_type=issue",
- "preview_note_path": "/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue"
-}
+ create_note_path: '/gitlab-org/gitlab-ce/notes?target_id=98&target_type=issue',
+ preview_note_path:
+ '/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
+};
export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
- 'GET': {
- '/gitlab-org/gitlab-ce/issues/26/discussions.json': [{
- "id": "0fb4e0e3f9276e55ff32eb4195add694aece4edd",
- "reply_id": "0fb4e0e3f9276e55ff32eb4195add694aece4edd",
- "expanded": true,
- "notes": [{
- "id": 1390,
- "attachment": {
- "url": null,
- "filename": null,
- "image": false
- },
- "author": {
- "id": 1,
- "name": "Root",
- "username": "root",
- "state": "active",
- "avatar_url": null,
- "path": "/root"
- },
- "created_at": "2017-08-01T17:09:33.762Z",
- "updated_at": "2017-08-01T17:09:33.762Z",
- "system": false,
- "noteable_id": 98,
- "noteable_type": "Issue",
- "type": null,
- "human_access": "Owner",
- "note": "sdfdsaf",
- "note_html": "\u003cp dir=\"auto\"\u003esdfdsaf\u003c/p\u003e",
- "current_user": {
- "can_edit": true
- },
- "discussion_id": "0fb4e0e3f9276e55ff32eb4195add694aece4edd",
- "emoji_awardable": true,
- "award_emoji": [{
- "name": "baseball",
- "user": {
- "id": 1,
- "name": "Root",
- "username": "root"
- }
- }, {
- "name": "art",
- "user": {
- "id": 1,
- "name": "Root",
- "username": "root"
- }
- }],
- "toggle_award_path": "/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji",
- "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390\u0026user_id=1",
- "path": "/gitlab-org/gitlab-ce/notes/1390"
- }],
- "individual_note": true
- }, {
- "id": "70d5c92a4039a36c70100c6691c18c27e4b0a790",
- "reply_id": "70d5c92a4039a36c70100c6691c18c27e4b0a790",
- "expanded": true,
- "notes": [{
- "id": 1391,
- "attachment": {
- "url": null,
- "filename": null,
- "image": false
- },
- "author": {
- "id": 1,
- "name": "Root",
- "username": "root",
- "state": "active",
- "avatar_url": null,
- "path": "/root"
- },
- "created_at": "2017-08-02T10:51:38.685Z",
- "updated_at": "2017-08-02T10:51:38.685Z",
- "system": false,
- "noteable_id": 98,
- "noteable_type": "Issue",
- "type": null,
- "human_access": "Owner",
- "note": "New note!",
- "note_html": "\u003cp dir=\"auto\"\u003eNew note!\u003c/p\u003e",
- "current_user": {
- "can_edit": true
- },
- "discussion_id": "70d5c92a4039a36c70100c6691c18c27e4b0a790",
- "emoji_awardable": true,
- "award_emoji": [],
- "toggle_award_path": "/gitlab-org/gitlab-ce/notes/1391/toggle_award_emoji",
- "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1391\u0026user_id=1",
- "path": "/gitlab-org/gitlab-ce/notes/1391"
- }],
- "individual_note": true
- }],
+ GET: {
+ '/gitlab-org/gitlab-ce/issues/26/discussions.json': [
+ {
+ id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
+ reply_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
+ expanded: true,
+ notes: [
+ {
+ id: 1390,
+ attachment: {
+ url: null,
+ filename: null,
+ image: false,
+ },
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
+ },
+ created_at: '2017-08-01T17:09:33.762Z',
+ updated_at: '2017-08-01T17:09:33.762Z',
+ system: false,
+ noteable_id: 98,
+ noteable_type: 'Issue',
+ type: null,
+ human_access: 'Owner',
+ note: 'sdfdsaf',
+ note_html: '\u003cp dir="auto"\u003esdfdsaf\u003c/p\u003e',
+ current_user: {
+ can_edit: true,
+ },
+ discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
+ emoji_awardable: true,
+ award_emoji: [
+ {
+ name: 'baseball',
+ user: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ },
+ },
+ {
+ name: 'art',
+ user: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ },
+ },
+ ],
+ toggle_award_path: '/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390\u0026user_id=1',
+ path: '/gitlab-org/gitlab-ce/notes/1390',
+ },
+ ],
+ individual_note: true,
+ },
+ {
+ id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
+ reply_id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
+ expanded: true,
+ notes: [
+ {
+ id: 1391,
+ attachment: {
+ url: null,
+ filename: null,
+ image: false,
+ },
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
+ },
+ created_at: '2017-08-02T10:51:38.685Z',
+ updated_at: '2017-08-02T10:51:38.685Z',
+ system: false,
+ noteable_id: 98,
+ noteable_type: 'Issue',
+ type: null,
+ human_access: 'Owner',
+ note: 'New note!',
+ note_html: '\u003cp dir="auto"\u003eNew note!\u003c/p\u003e',
+ current_user: {
+ can_edit: true,
+ },
+ discussion_id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-ce/notes/1391/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1391\u0026user_id=1',
+ path: '/gitlab-org/gitlab-ce/notes/1391',
+ },
+ ],
+ individual_note: true,
+ },
+ ],
'/gitlab-org/gitlab-ce/noteable/issue/98/notes': {
last_fetched_at: 1512900838,
notes: [],
},
},
- 'PUT': {
+ PUT: {
'/gitlab-org/gitlab-ce/notes/1471': {
- "commands_changes": null,
- "valid": true,
- "id": 1471,
- "attachment": null,
- "author": {
- "id": 1,
- "name": "Root",
- "username": "root",
- "state": "active",
- "avatar_url": null,
- "path": "/root"
+ commands_changes: null,
+ valid: true,
+ id: 1471,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
},
- "created_at": "2017-08-08T16:53:00.666Z",
- "updated_at": "2017-12-10T11:03:21.876Z",
- "system": false,
- "noteable_id": 124,
- "noteable_type": "Issue",
- "noteable_iid": 29,
- "type": "DiscussionNote",
- "human_access": "Owner",
- "note": "Adding a comment",
- "note_html": "\u003cp dir=\"auto\"\u003eAdding a comment\u003c/p\u003e",
- "last_edited_at": "2017-12-10T11:03:21.876Z",
- "last_edited_by": {
- "id": 1,
- "name": 'Root',
- "username": 'root',
- "state": 'active',
- "avatar_url": null,
- "path": '/root',
+ created_at: '2017-08-08T16:53:00.666Z',
+ updated_at: '2017-12-10T11:03:21.876Z',
+ system: false,
+ noteable_id: 124,
+ noteable_type: 'Issue',
+ noteable_iid: 29,
+ type: 'DiscussionNote',
+ human_access: 'Owner',
+ note: 'Adding a comment',
+ note_html: '\u003cp dir="auto"\u003eAdding a comment\u003c/p\u003e',
+ last_edited_at: '2017-12-10T11:03:21.876Z',
+ last_edited_by: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
},
- "current_user": {
- "can_edit": true
+ current_user: {
+ can_edit: true,
},
- "discussion_id": "a3ed36e29b1957efb3b68c53e2d7a2b24b1df052",
- "emoji_awardable": true,
- "award_emoji": [],
- "toggle_award_path": "/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji",
- "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1",
- "path": "/gitlab-org/gitlab-ce/notes/1471"
+ discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1',
+ path: '/gitlab-org/gitlab-ce/notes/1471',
},
- }
+ },
};
export const DISCUSSION_NOTE_RESPONSE_MAP = {
...INDIVIDUAL_NOTE_RESPONSE_MAP,
- 'GET': {
+ GET: {
...INDIVIDUAL_NOTE_RESPONSE_MAP.GET,
- '/gitlab-org/gitlab-ce/issues/26/discussions.json': [{
- "id": "a3ed36e29b1957efb3b68c53e2d7a2b24b1df052",
- "reply_id": "a3ed36e29b1957efb3b68c53e2d7a2b24b1df052",
- "expanded": true,
- "notes": [{
- "id": 1471,
- "attachment": {
- "url": null,
- "filename": null,
- "image": false
- },
- "author": {
- "id": 1,
- "name": "Root",
- "username": "root",
- "state": "active",
- "avatar_url": null,
- "path": "/root"
- },
- "created_at": "2017-08-08T16:53:00.666Z",
- "updated_at": "2017-08-08T16:53:00.666Z",
- "system": false,
- "noteable_id": 124,
- "noteable_type": "Issue",
- "noteable_iid": 29,
- "type": "DiscussionNote",
- "human_access": "Owner",
- "note": "Adding a comment",
- "note_html": "\u003cp dir=\"auto\"\u003eAdding a comment\u003c/p\u003e",
- "current_user": {
- "can_edit": true
- },
- "discussion_id": "a3ed36e29b1957efb3b68c53e2d7a2b24b1df052",
- "emoji_awardable": true,
- "award_emoji": [],
- "toggle_award_path": "/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji",
- "report_abuse_path": "/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1",
- "path": "/gitlab-org/gitlab-ce/notes/1471"
- }],
- "individual_note": false
- }],
+ '/gitlab-org/gitlab-ce/issues/26/discussions.json': [
+ {
+ id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
+ reply_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
+ expanded: true,
+ notes: [
+ {
+ id: 1471,
+ attachment: {
+ url: null,
+ filename: null,
+ image: false,
+ },
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ path: '/root',
+ },
+ created_at: '2017-08-08T16:53:00.666Z',
+ updated_at: '2017-08-08T16:53:00.666Z',
+ system: false,
+ noteable_id: 124,
+ noteable_type: 'Issue',
+ noteable_iid: 29,
+ type: 'DiscussionNote',
+ human_access: 'Owner',
+ note: 'Adding a comment',
+ note_html: '\u003cp dir="auto"\u003eAdding a comment\u003c/p\u003e',
+ current_user: {
+ can_edit: true,
+ },
+ discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1',
+ path: '/gitlab-org/gitlab-ce/notes/1471',
+ },
+ ],
+ individual_note: false,
+ },
+ ],
},
};
export function individualNoteInterceptor(request, next) {
const body = INDIVIDUAL_NOTE_RESPONSE_MAP[request.method.toUpperCase()][request.url];
- next(request.respondWith(JSON.stringify(body), {
- status: 200,
- }));
+ next(
+ request.respondWith(JSON.stringify(body), {
+ status: 200,
+ }),
+ );
}
export function discussionNoteInterceptor(request, next) {
const body = DISCUSSION_NOTE_RESPONSE_MAP[request.method.toUpperCase()][request.url];
- next(request.respondWith(JSON.stringify(body), {
- status: 200,
- }));
+ next(
+ request.respondWith(JSON.stringify(body), {
+ status: 200,
+ }),
+ );
}
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 91249b2c79e..520a25cc5c6 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -5,7 +5,13 @@ import * as actions from '~/notes/stores/actions';
import store from '~/notes/stores';
import testAction from '../../helpers/vuex_action_helper';
import { resetStore } from '../helpers';
-import { discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
+import {
+ discussionMock,
+ notesDataMock,
+ userDataMock,
+ noteableDataMock,
+ individualNote,
+} from '../mock_data';
describe('Actions Notes Store', () => {
afterEach(() => {
@@ -13,66 +19,103 @@ describe('Actions Notes Store', () => {
});
describe('setNotesData', () => {
- it('should set received notes data', (done) => {
- testAction(actions.setNotesData, null, { notesData: {} }, [
- { type: 'SET_NOTES_DATA', payload: notesDataMock },
- ], done);
+ it('should set received notes data', done => {
+ testAction(
+ actions.setNotesData,
+ notesDataMock,
+ { notesData: {} },
+ [{ type: 'SET_NOTES_DATA', payload: notesDataMock }],
+ [],
+ done,
+ );
});
});
describe('setNoteableData', () => {
- it('should set received issue data', (done) => {
- testAction(actions.setNoteableData, null, { noteableData: {} }, [
- { type: 'SET_NOTEABLE_DATA', payload: noteableDataMock },
- ], done);
+ it('should set received issue data', done => {
+ testAction(
+ actions.setNoteableData,
+ noteableDataMock,
+ { noteableData: {} },
+ [{ type: 'SET_NOTEABLE_DATA', payload: noteableDataMock }],
+ [],
+ done,
+ );
});
});
describe('setUserData', () => {
- it('should set received user data', (done) => {
- testAction(actions.setUserData, null, { userData: {} }, [
- { type: 'SET_USER_DATA', payload: userDataMock },
- ], done);
+ it('should set received user data', done => {
+ testAction(
+ actions.setUserData,
+ userDataMock,
+ { userData: {} },
+ [{ type: 'SET_USER_DATA', payload: userDataMock }],
+ [],
+ done,
+ );
});
});
describe('setLastFetchedAt', () => {
- it('should set received timestamp', (done) => {
- testAction(actions.setLastFetchedAt, null, { lastFetchedAt: {} }, [
- { type: 'SET_LAST_FETCHED_AT', payload: 'timestamp' },
- ], done);
+ it('should set received timestamp', done => {
+ testAction(
+ actions.setLastFetchedAt,
+ 'timestamp',
+ { lastFetchedAt: {} },
+ [{ type: 'SET_LAST_FETCHED_AT', payload: 'timestamp' }],
+ [],
+ done,
+ );
});
});
describe('setInitialNotes', () => {
- it('should set initial notes', (done) => {
- testAction(actions.setInitialNotes, null, { notes: [] }, [
- { type: 'SET_INITIAL_NOTES', payload: [individualNote] },
- ], done);
+ it('should set initial notes', done => {
+ testAction(
+ actions.setInitialNotes,
+ [individualNote],
+ { notes: [] },
+ [{ type: 'SET_INITIAL_NOTES', payload: [individualNote] }],
+ [],
+ done,
+ );
});
});
describe('setTargetNoteHash', () => {
- it('should set target note hash', (done) => {
- testAction(actions.setTargetNoteHash, null, { notes: [] }, [
- { type: 'SET_TARGET_NOTE_HASH', payload: 'hash' },
- ], done);
+ it('should set target note hash', done => {
+ testAction(
+ actions.setTargetNoteHash,
+ 'hash',
+ { notes: [] },
+ [{ type: 'SET_TARGET_NOTE_HASH', payload: 'hash' }],
+ [],
+ done,
+ );
});
});
describe('toggleDiscussion', () => {
- it('should toggle discussion', (done) => {
- testAction(actions.toggleDiscussion, null, { notes: [discussionMock] }, [
- { type: 'TOGGLE_DISCUSSION', payload: { discussionId: discussionMock.id } },
- ], done);
+ it('should toggle discussion', done => {
+ testAction(
+ actions.toggleDiscussion,
+ { discussionId: discussionMock.id },
+ { notes: [discussionMock] },
+ [{ type: 'TOGGLE_DISCUSSION', payload: { discussionId: discussionMock.id } }],
+ [],
+ done,
+ );
});
});
describe('async methods', () => {
const interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify({}), {
- status: 200,
- }));
+ next(
+ request.respondWith(JSON.stringify({}), {
+ status: 200,
+ }),
+ );
};
beforeEach(() => {
@@ -84,8 +127,9 @@ describe('Actions Notes Store', () => {
});
describe('closeIssue', () => {
- it('sets state as closed', (done) => {
- store.dispatch('closeIssue', { notesData: { closeIssuePath: '' } })
+ it('sets state as closed', done => {
+ store
+ .dispatch('closeIssue', { notesData: { closeIssuePath: '' } })
.then(() => {
expect(store.state.noteableData.state).toEqual('closed');
expect(store.state.isToggleStateButtonLoading).toEqual(false);
@@ -96,8 +140,9 @@ describe('Actions Notes Store', () => {
});
describe('reopenIssue', () => {
- it('sets state as reopened', (done) => {
- store.dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } })
+ it('sets state as reopened', done => {
+ store
+ .dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } })
.then(() => {
expect(store.state.noteableData.state).toEqual('reopened');
expect(store.state.isToggleStateButtonLoading).toEqual(false);
@@ -110,7 +155,7 @@ describe('Actions Notes Store', () => {
describe('emitStateChangedEvent', () => {
it('emits an event on the document', () => {
- document.addEventListener('issuable_vue_app:change', (event) => {
+ document.addEventListener('issuable_vue_app:change', event => {
expect(event.detail.data).toEqual({ id: '1', state: 'closed' });
expect(event.detail.isClosed).toEqual(false);
});
@@ -120,40 +165,47 @@ describe('Actions Notes Store', () => {
});
describe('toggleStateButtonLoading', () => {
- it('should set loading as true', (done) => {
- testAction(actions.toggleStateButtonLoading, true, {}, [
- { type: 'TOGGLE_STATE_BUTTON_LOADING', payload: true },
- ], done);
+ it('should set loading as true', done => {
+ testAction(
+ actions.toggleStateButtonLoading,
+ true,
+ {},
+ [{ type: 'TOGGLE_STATE_BUTTON_LOADING', payload: true }],
+ [],
+ done,
+ );
});
- it('should set loading as false', (done) => {
- testAction(actions.toggleStateButtonLoading, false, {}, [
- { type: 'TOGGLE_STATE_BUTTON_LOADING', payload: false },
- ], done);
+ it('should set loading as false', done => {
+ testAction(
+ actions.toggleStateButtonLoading,
+ false,
+ {},
+ [{ type: 'TOGGLE_STATE_BUTTON_LOADING', payload: false }],
+ [],
+ done,
+ );
});
});
describe('toggleIssueLocalState', () => {
- it('sets issue state as closed', (done) => {
- testAction(actions.toggleIssueLocalState, 'closed', {}, [
- { type: 'CLOSE_ISSUE', payload: 'closed' },
- ], done);
+ it('sets issue state as closed', done => {
+ testAction(actions.toggleIssueLocalState, 'closed', {}, [{ type: 'CLOSE_ISSUE' }], [], done);
});
- it('sets issue state as reopened', (done) => {
- testAction(actions.toggleIssueLocalState, 'reopened', {}, [
- { type: 'REOPEN_ISSUE', payload: 'reopened' },
- ], done);
+ it('sets issue state as reopened', done => {
+ testAction(actions.toggleIssueLocalState, 'reopened', {}, [{ type: 'REOPEN_ISSUE' }], [], done);
});
});
describe('poll', () => {
- beforeEach((done) => {
+ beforeEach(done => {
jasmine.clock().install();
spyOn(Vue.http, 'get').and.callThrough();
- store.dispatch('setNotesData', notesDataMock)
+ store
+ .dispatch('setNotesData', notesDataMock)
.then(done)
.catch(done.fail);
});
@@ -162,23 +214,29 @@ describe('Actions Notes Store', () => {
jasmine.clock().uninstall();
});
- it('calls service with last fetched state', (done) => {
+ it('calls service with last fetched state', done => {
const interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify({
- notes: [],
- last_fetched_at: '123456',
- }), {
- status: 200,
- headers: {
- 'poll-interval': '1000',
- },
- }));
+ next(
+ request.respondWith(
+ JSON.stringify({
+ notes: [],
+ last_fetched_at: '123456',
+ }),
+ {
+ status: 200,
+ headers: {
+ 'poll-interval': '1000',
+ },
+ },
+ ),
+ );
};
Vue.http.interceptors.push(interceptor);
Vue.http.interceptors.push(headersInterceptor);
- store.dispatch('poll')
+ store
+ .dispatch('poll')
.then(() => new Promise(resolve => requestAnimationFrame(resolve)))
.then(() => {
expect(Vue.http.get).toHaveBeenCalledWith(jasmine.anything(), {
@@ -192,9 +250,12 @@ describe('Actions Notes Store', () => {
jasmine.clock().tick(1500);
})
- .then(() => new Promise((resolve) => {
- requestAnimationFrame(resolve);
- }))
+ .then(
+ () =>
+ new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ }),
+ )
.then(() => {
expect(Vue.http.get.calls.count()).toBe(2);
expect(Vue.http.get.calls.mostRecent().args[1].headers).toEqual({
diff --git a/spec/javascripts/pages/labels/components/promote_label_modal_spec.js b/spec/javascripts/pages/labels/components/promote_label_modal_spec.js
index 080158a8ee0..a24f8204fe1 100644
--- a/spec/javascripts/pages/labels/components/promote_label_modal_spec.js
+++ b/spec/javascripts/pages/labels/components/promote_label_modal_spec.js
@@ -12,6 +12,7 @@ describe('Promote label modal', () => {
labelColor: '#5cb85c',
labelTextColor: '#ffffff',
url: `${gl.TEST_HOST}/dummy/promote/labels`,
+ groupName: 'group',
};
describe('Modal title and description', () => {
@@ -24,7 +25,7 @@ describe('Promote label modal', () => {
});
it('contains the proper description', () => {
- expect(vm.text).toContain('Promoting this label will make it available for all projects inside the group');
+ expect(vm.text).toContain(`Promoting ${labelMockData.labelTitle} will make it available for all projects inside ${labelMockData.groupName}`);
});
it('contains a label span with the color', () => {
diff --git a/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js b/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
index 22956929e7b..8b220423637 100644
--- a/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
+++ b/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
@@ -10,6 +10,7 @@ describe('Promote milestone modal', () => {
const milestoneMockData = {
milestoneTitle: 'v1.0',
url: `${gl.TEST_HOST}/dummy/promote/milestones`,
+ groupName: 'group',
};
describe('Modal title and description', () => {
@@ -22,7 +23,7 @@ describe('Promote milestone modal', () => {
});
it('contains the proper description', () => {
- expect(vm.text).toContain('Promoting this milestone will make it available for all projects inside the group.');
+ expect(vm.text).toContain(`Promoting ${milestoneMockData.milestoneTitle} will make it available for all projects inside ${milestoneMockData.groupName}.`);
});
it('contains the correct title', () => {
diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js
index b9494f86d74..70eba98e939 100644
--- a/spec/javascripts/pipelines/graph/mock_data.js
+++ b/spec/javascripts/pipelines/graph/mock_data.js
@@ -1,232 +1,261 @@
-/* eslint-disable quote-props, quotes, comma-dangle */
export default {
- "id": 123,
- "user": {
- "name": "Root",
- "username": "root",
- "id": 1,
- "state": "active",
- "avatar_url": null,
- "web_url": "http://localhost:3000/root"
+ id: 123,
+ user: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
},
- "active": false,
- "coverage": null,
- "path": "/root/ci-mock/pipelines/123",
- "details": {
- "status": {
- "icon": "icon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/ci-mock/pipelines/123",
- "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico"
+ active: false,
+ coverage: null,
+ path: '/root/ci-mock/pipelines/123',
+ details: {
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/pipelines/123',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
},
- "duration": 9,
- "finished_at": "2017-04-19T14:30:27.542Z",
- "stages": [{
- "name": "test",
- "title": "test: passed",
- "groups": [{
- "name": "test",
- "size": 1,
- "status": {
- "icon": "icon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/ci-mock/builds/4153",
- "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
- "action": {
- "icon": "retry",
- "title": "Retry",
- "path": "/root/ci-mock/builds/4153/retry",
- "method": "post"
- }
+ duration: 9,
+ finished_at: '2017-04-19T14:30:27.542Z',
+ stages: [
+ {
+ name: 'test',
+ title: 'test: passed',
+ groups: [
+ {
+ name: 'test',
+ size: 1,
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4153',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4153/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 4153,
+ name: 'test',
+ build_path: '/root/ci-mock/builds/4153',
+ retry_path: '/root/ci-mock/builds/4153/retry',
+ playable: false,
+ created_at: '2017-04-13T09:25:18.959Z',
+ updated_at: '2017-04-13T09:25:23.118Z',
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4153',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4153/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/pipelines/123#test',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
},
- "jobs": [{
- "id": 4153,
- "name": "test",
- "build_path": "/root/ci-mock/builds/4153",
- "retry_path": "/root/ci-mock/builds/4153/retry",
- "playable": false,
- "created_at": "2017-04-13T09:25:18.959Z",
- "updated_at": "2017-04-13T09:25:23.118Z",
- "status": {
- "icon": "icon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/ci-mock/builds/4153",
- "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
- "action": {
- "icon": "retry",
- "title": "Retry",
- "path": "/root/ci-mock/builds/4153/retry",
- "method": "post"
- }
- }
- }]
- }],
- "status": {
- "icon": "icon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/ci-mock/pipelines/123#test",
- "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico"
+ path: '/root/ci-mock/pipelines/123#test',
+ dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test',
},
- "path": "/root/ci-mock/pipelines/123#test",
- "dropdown_path": "/root/ci-mock/pipelines/123/stage.json?stage=test"
- }, {
- "name": "deploy",
- "title": "deploy: passed",
- "groups": [{
- "name": "deploy to production",
- "size": 1,
- "status": {
- "icon": "icon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/ci-mock/builds/4166",
- "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
- "action": {
- "icon": "retry",
- "title": "Retry",
- "path": "/root/ci-mock/builds/4166/retry",
- "method": "post"
- }
+ {
+ name: 'deploy',
+ title: 'deploy: passed',
+ groups: [
+ {
+ name: 'deploy to production',
+ size: 1,
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4166',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4166/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 4166,
+ name: 'deploy to production',
+ build_path: '/root/ci-mock/builds/4166',
+ retry_path: '/root/ci-mock/builds/4166/retry',
+ playable: false,
+ created_at: '2017-04-19T14:29:46.463Z',
+ updated_at: '2017-04-19T14:30:27.498Z',
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4166',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4166/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ {
+ name: 'deploy to staging',
+ size: 1,
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4159',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4159/retry',
+ method: 'post',
+ },
+ },
+ jobs: [
+ {
+ id: 4159,
+ name: 'deploy to staging',
+ build_path: '/root/ci-mock/builds/4159',
+ retry_path: '/root/ci-mock/builds/4159/retry',
+ playable: false,
+ created_at: '2017-04-18T16:32:08.420Z',
+ updated_at: '2017-04-18T16:32:12.631Z',
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/builds/4159',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4159/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/pipelines/123#deploy',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
},
- "jobs": [{
- "id": 4166,
- "name": "deploy to production",
- "build_path": "/root/ci-mock/builds/4166",
- "retry_path": "/root/ci-mock/builds/4166/retry",
- "playable": false,
- "created_at": "2017-04-19T14:29:46.463Z",
- "updated_at": "2017-04-19T14:30:27.498Z",
- "status": {
- "icon": "icon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/ci-mock/builds/4166",
- "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
- "action": {
- "icon": "retry",
- "title": "Retry",
- "path": "/root/ci-mock/builds/4166/retry",
- "method": "post"
- }
- }
- }]
- }, {
- "name": "deploy to staging",
- "size": 1,
- "status": {
- "icon": "icon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/ci-mock/builds/4159",
- "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
- "action": {
- "icon": "retry",
- "title": "Retry",
- "path": "/root/ci-mock/builds/4159/retry",
- "method": "post"
- }
- },
- "jobs": [{
- "id": 4159,
- "name": "deploy to staging",
- "build_path": "/root/ci-mock/builds/4159",
- "retry_path": "/root/ci-mock/builds/4159/retry",
- "playable": false,
- "created_at": "2017-04-18T16:32:08.420Z",
- "updated_at": "2017-04-18T16:32:12.631Z",
- "status": {
- "icon": "icon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/ci-mock/builds/4159",
- "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
- "action": {
- "icon": "retry",
- "title": "Retry",
- "path": "/root/ci-mock/builds/4159/retry",
- "method": "post"
- }
- }
- }]
- }],
- "status": {
- "icon": "icon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/ci-mock/pipelines/123#deploy",
- "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico"
+ path: '/root/ci-mock/pipelines/123#deploy',
+ dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=deploy',
+ },
+ ],
+ artifacts: [],
+ manual_actions: [
+ {
+ name: 'deploy to production',
+ path: '/root/ci-mock/builds/4166/play',
+ playable: false,
},
- "path": "/root/ci-mock/pipelines/123#deploy",
- "dropdown_path": "/root/ci-mock/pipelines/123/stage.json?stage=deploy"
- }],
- "artifacts": [],
- "manual_actions": [{
- "name": "deploy to production",
- "path": "/root/ci-mock/builds/4166/play",
- "playable": false
- }]
+ ],
},
- "flags": {
- "latest": true,
- "triggered": false,
- "stuck": false,
- "yaml_errors": false,
- "retryable": false,
- "cancelable": false
+ flags: {
+ latest: true,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: false,
+ cancelable: false,
},
- "ref": {
- "name": "master",
- "path": "/root/ci-mock/tree/master",
- "tag": false,
- "branch": true
+ ref: {
+ name: 'master',
+ path: '/root/ci-mock/tree/master',
+ tag: false,
+ branch: true,
},
- "commit": {
- "id": "798e5f902592192afaba73f4668ae30e56eae492",
- "short_id": "798e5f90",
- "title": "Merge branch 'new-branch' into 'master'\r",
- "created_at": "2017-04-13T10:25:17.000+01:00",
- "parent_ids": ["54d483b1ed156fbbf618886ddf7ab023e24f8738", "c8e2d38a6c538822e81c57022a6e3a0cfedebbcc"],
- "message": "Merge branch 'new-branch' into 'master'\r\n\r\nAdd new file\r\n\r\nSee merge request !1",
- "author_name": "Root",
- "author_email": "admin@example.com",
- "authored_date": "2017-04-13T10:25:17.000+01:00",
- "committer_name": "Root",
- "committer_email": "admin@example.com",
- "committed_date": "2017-04-13T10:25:17.000+01:00",
- "author": {
- "name": "Root",
- "username": "root",
- "id": 1,
- "state": "active",
- "avatar_url": null,
- "web_url": "http://localhost:3000/root"
+ commit: {
+ id: '798e5f902592192afaba73f4668ae30e56eae492',
+ short_id: '798e5f90',
+ title: "Merge branch 'new-branch' into 'master'\r",
+ created_at: '2017-04-13T10:25:17.000+01:00',
+ parent_ids: [
+ '54d483b1ed156fbbf618886ddf7ab023e24f8738',
+ 'c8e2d38a6c538822e81c57022a6e3a0cfedebbcc',
+ ],
+ message:
+ "Merge branch 'new-branch' into 'master'\r\n\r\nAdd new file\r\n\r\nSee merge request !1",
+ author_name: 'Root',
+ author_email: 'admin@example.com',
+ authored_date: '2017-04-13T10:25:17.000+01:00',
+ committer_name: 'Root',
+ committer_email: 'admin@example.com',
+ committed_date: '2017-04-13T10:25:17.000+01:00',
+ author: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
},
- "author_gravatar_url": null,
- "commit_url": "http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492",
- "commit_path": "/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492"
+ author_gravatar_url: null,
+ commit_url:
+ 'http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492',
+ commit_path: '/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492',
},
- "created_at": "2017-04-13T09:25:18.881Z",
- "updated_at": "2017-04-19T14:30:27.561Z"
+ created_at: '2017-04-13T09:25:18.881Z',
+ updated_at: '2017-04-19T14:30:27.561Z',
};
diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js
index 3c9da4f107b..bc4c444655a 100644
--- a/spec/javascripts/registry/stores/actions_spec.js
+++ b/spec/javascripts/registry/stores/actions_spec.js
@@ -29,57 +29,96 @@ describe('Actions Registry Store', () => {
describe('fetchRepos', () => {
beforeEach(() => {
interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify(reposServerResponse), {
- status: 200,
- }));
+ next(
+ request.respondWith(JSON.stringify(reposServerResponse), {
+ status: 200,
+ }),
+ );
};
Vue.http.interceptors.push(interceptor);
});
- it('should set receveived repos', (done) => {
- testAction(actions.fetchRepos, null, mockedState, [
- { type: types.TOGGLE_MAIN_LOADING },
- { type: types.SET_REPOS_LIST, payload: reposServerResponse },
- ], done);
+ it('should set receveived repos', done => {
+ testAction(
+ actions.fetchRepos,
+ null,
+ mockedState,
+ [
+ { type: types.TOGGLE_MAIN_LOADING },
+ { type: types.TOGGLE_MAIN_LOADING },
+ { type: types.SET_REPOS_LIST, payload: reposServerResponse },
+ ],
+ [],
+ done,
+ );
});
});
describe('fetchList', () => {
beforeEach(() => {
interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify(registryServerResponse), {
- status: 200,
- }));
+ next(
+ request.respondWith(JSON.stringify(registryServerResponse), {
+ status: 200,
+ }),
+ );
};
Vue.http.interceptors.push(interceptor);
});
- it('should set received list', (done) => {
+ it('should set received list', done => {
mockedState.repos = parsedReposServerResponse;
- testAction(actions.fetchList, { repo: mockedState.repos[1] }, mockedState, [
- { type: types.TOGGLE_REGISTRY_LIST_LOADING },
- { type: types.SET_REGISTRY_LIST, payload: registryServerResponse },
- ], done);
+ const repo = mockedState.repos[1];
+
+ testAction(
+ actions.fetchList,
+ { repo },
+ mockedState,
+ [
+ { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
+ { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
+ {
+ type: types.SET_REGISTRY_LIST,
+ payload: {
+ repo,
+ resp: registryServerResponse,
+ headers: jasmine.anything(),
+ },
+ },
+ ],
+ [],
+ done,
+ );
});
});
});
describe('setMainEndpoint', () => {
- it('should commit set main endpoint', (done) => {
- testAction(actions.setMainEndpoint, 'endpoint', mockedState, [
- { type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' },
- ], done);
+ it('should commit set main endpoint', done => {
+ testAction(
+ actions.setMainEndpoint,
+ 'endpoint',
+ mockedState,
+ [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }],
+ [],
+ done,
+ );
});
});
describe('toggleLoading', () => {
- it('should commit toggle main loading', (done) => {
- testAction(actions.toggleLoading, null, mockedState, [
- { type: types.TOGGLE_MAIN_LOADING },
- ], done);
+ it('should commit toggle main loading', done => {
+ testAction(
+ actions.toggleLoading,
+ null,
+ mockedState,
+ [{ type: types.TOGGLE_MAIN_LOADING }],
+ [],
+ done,
+ );
});
});
});
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
index 88a33caf2e3..0c173062835 100644
--- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
@@ -62,4 +62,22 @@ describe('Confidential Issue Sidebar Block', () => {
done();
});
});
+
+ it('displays the edit form when opened from collapsed state', (done) => {
+ expect(vm1.edit).toBe(false);
+
+ vm1.$el.querySelector('.sidebar-collapsed-icon').click();
+
+ expect(vm1.edit).toBe(true);
+
+ setTimeout(() => {
+ expect(
+ vm1.$el
+ .innerHTML
+ .includes('You are going to turn off the confidentiality.'),
+ ).toBe(true);
+
+ done();
+ });
+ });
});
diff --git a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
index 696fca516bc..9abc3daf221 100644
--- a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
@@ -68,4 +68,22 @@ describe('LockIssueSidebar', () => {
done();
});
});
+
+ it('displays the edit form when opened from collapsed state', (done) => {
+ expect(vm1.isLockDialogOpen).toBe(false);
+
+ vm1.$el.querySelector('.sidebar-collapsed-icon').click();
+
+ expect(vm1.isLockDialogOpen).toBe(true);
+
+ setTimeout(() => {
+ expect(
+ vm1.$el
+ .innerHTML
+ .includes('Unlock this issue?'),
+ ).toBe(true);
+
+ done();
+ });
+ });
});
diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js
index d9e84e35f69..8b6e8b24f00 100644
--- a/spec/javascripts/sidebar/mock_data.js
+++ b/spec/javascripts/sidebar/mock_data.js
@@ -1,7 +1,5 @@
-/* eslint-disable quote-props*/
-
const RESPONSE_MAP = {
- 'GET': {
+ GET: {
'/gitlab-org/gitlab-shell/issues/5.json': {
id: 45,
iid: 5,
@@ -27,7 +25,8 @@ const RESPONSE_MAP = {
username: 'user0',
id: 22,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/user0',
},
{
@@ -35,7 +34,8 @@ const RESPONSE_MAP = {
username: 'tajuana',
id: 18,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/tajuana',
},
{
@@ -43,7 +43,8 @@ const RESPONSE_MAP = {
username: 'michaele.will',
id: 16,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/michaele.will',
},
],
@@ -72,7 +73,8 @@ const RESPONSE_MAP = {
username: 'user0',
id: 22,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
web_url: 'http://localhost:3001/user0',
},
{
@@ -80,7 +82,8 @@ const RESPONSE_MAP = {
username: 'tajuana',
id: 18,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
web_url: 'http://localhost:3001/tajuana',
},
{
@@ -88,7 +91,8 @@ const RESPONSE_MAP = {
username: 'michaele.will',
id: 16,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
web_url: 'http://localhost:3001/michaele.will',
},
],
@@ -100,7 +104,8 @@ const RESPONSE_MAP = {
username: 'user0',
id: 22,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
web_url: 'http://localhost:3001/user0',
},
{
@@ -108,7 +113,8 @@ const RESPONSE_MAP = {
username: 'tajuana',
id: 18,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
web_url: 'http://localhost:3001/tajuana',
},
{
@@ -116,7 +122,8 @@ const RESPONSE_MAP = {
username: 'michaele.will',
id: 16,
state: 'active',
- avatar_url: 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
web_url: 'http://localhost:3001/michaele.will',
},
],
@@ -126,20 +133,21 @@ const RESPONSE_MAP = {
},
'/autocomplete/projects?project_id=15': [
{
- 'id': 0,
- 'name_with_namespace': 'No project',
- }, {
- 'id': 20,
- 'name_with_namespace': 'foo / bar',
+ id: 0,
+ name_with_namespace: 'No project',
+ },
+ {
+ id: 20,
+ name_with_namespace: 'foo / bar',
},
],
},
- 'PUT': {
+ PUT: {
'/gitlab-org/gitlab-shell/issues/5.json': {
data: {},
},
},
- 'POST': {
+ POST: {
'/gitlab-org/gitlab-shell/issues/5/move': {
id: 123,
iid: 5,
@@ -182,7 +190,8 @@ const mockData = {
id: 1,
name: 'Administrator',
username: 'root',
- avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
},
rootPath: '/',
fullPath: '/gitlab-org/gitlab-shell',
@@ -201,12 +210,14 @@ const mockData = {
},
};
-mockData.sidebarMockInterceptor = function (request, next) {
+mockData.sidebarMockInterceptor = function(request, next) {
const body = this.responseMap[request.method.toUpperCase()][request.url];
- next(request.respondWith(JSON.stringify(body), {
- status: 200,
- }));
+ next(
+ request.respondWith(JSON.stringify(body), {
+ status: 200,
+ }),
+ );
}.bind(mockData);
export default mockData;
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 235c33fac0d..9b9c9656979 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -17,46 +17,58 @@ describe('MRWidgetHeader', () => {
describe('computed', () => {
describe('shouldShowCommitsBehindText', () => {
it('return true when there are divergedCommitsCount', () => {
- vm = mountComponent(Component, { mr: {
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
- targetBranch: 'master',
- } });
+ vm = mountComponent(Component, {
+ mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+ targetBranch: 'master',
+ statusPath: 'abc',
+ },
+ });
expect(vm.shouldShowCommitsBehindText).toEqual(true);
});
it('returns false where there are no divergedComits count', () => {
- vm = mountComponent(Component, { mr: {
- divergedCommitsCount: 0,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
- targetBranch: 'master',
- } });
+ vm = mountComponent(Component, {
+ mr: {
+ divergedCommitsCount: 0,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+ targetBranch: 'master',
+ statusPath: 'abc',
+ },
+ });
expect(vm.shouldShowCommitsBehindText).toEqual(false);
});
});
describe('commitsText', () => {
it('returns singular when there is one commit', () => {
- vm = mountComponent(Component, { mr: {
- divergedCommitsCount: 1,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
- targetBranch: 'master',
- } });
+ vm = mountComponent(Component, {
+ mr: {
+ divergedCommitsCount: 1,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+ targetBranch: 'master',
+ statusPath: 'abc',
+ },
+ });
expect(vm.commitsText).toEqual('1 commit behind');
});
it('returns plural when there is more than one commit', () => {
- vm = mountComponent(Component, { mr: {
- divergedCommitsCount: 2,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
- targetBranch: 'master',
- } });
+ vm = mountComponent(Component, {
+ mr: {
+ divergedCommitsCount: 2,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+ targetBranch: 'master',
+ statusPath: 'abc',
+ },
+ });
expect(vm.commitsText).toEqual('2 commits behind');
});
@@ -66,24 +78,27 @@ describe('MRWidgetHeader', () => {
describe('template', () => {
describe('common elements', () => {
beforeEach(() => {
- vm = mountComponent(Component, { mr: {
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
- sourceBranchRemoved: false,
- targetBranchPath: 'foo/bar/commits-path',
- targetBranchTreePath: 'foo/bar/tree/path',
- targetBranch: 'master',
- isOpen: true,
- emailPatchesPath: '/mr/email-patches',
- plainDiffPath: '/mr/plainDiffPath',
- } });
+ vm = mountComponent(Component, {
+ mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ statusPath: 'abc',
+ },
+ });
});
it('renders source branch link', () => {
- expect(
- vm.$el.querySelector('.js-source-branch').innerHTML,
- ).toEqual('<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>');
+ expect(vm.$el.querySelector('.js-source-branch').innerHTML).toEqual(
+ '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ );
});
it('renders clipboard button', () => {
@@ -101,18 +116,21 @@ describe('MRWidgetHeader', () => {
});
beforeEach(() => {
- vm = mountComponent(Component, { mr: {
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
- sourceBranchRemoved: false,
- targetBranchPath: 'foo/bar/commits-path',
- targetBranchTreePath: 'foo/bar/tree/path',
- targetBranch: 'master',
- isOpen: true,
- emailPatchesPath: '/mr/email-patches',
- plainDiffPath: '/mr/plainDiffPath',
- } });
+ vm = mountComponent(Component, {
+ mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ statusPath: 'abc',
+ },
+ });
});
it('renders checkout branch button with modal trigger', () => {
@@ -123,39 +141,49 @@ describe('MRWidgetHeader', () => {
expect(button.getAttribute('data-toggle')).toEqual('modal');
});
+ it('renders web ide button', () => {
+ const button = vm.$el.querySelector('.js-web-ide');
+
+ expect(button.textContent.trim()).toEqual('Web IDE');
+ expect(button.getAttribute('href')).toEqual('undefined/-/ide/projectabc');
+ });
+
it('renders download dropdown with links', () => {
- expect(
- vm.$el.querySelector('.js-download-email-patches').textContent.trim(),
- ).toEqual('Email patches');
+ expect(vm.$el.querySelector('.js-download-email-patches').textContent.trim()).toEqual(
+ 'Email patches',
+ );
- expect(
- vm.$el.querySelector('.js-download-email-patches').getAttribute('href'),
- ).toEqual('/mr/email-patches');
+ expect(vm.$el.querySelector('.js-download-email-patches').getAttribute('href')).toEqual(
+ '/mr/email-patches',
+ );
- expect(
- vm.$el.querySelector('.js-download-plain-diff').textContent.trim(),
- ).toEqual('Plain diff');
+ expect(vm.$el.querySelector('.js-download-plain-diff').textContent.trim()).toEqual(
+ 'Plain diff',
+ );
- expect(
- vm.$el.querySelector('.js-download-plain-diff').getAttribute('href'),
- ).toEqual('/mr/plainDiffPath');
+ expect(vm.$el.querySelector('.js-download-plain-diff').getAttribute('href')).toEqual(
+ '/mr/plainDiffPath',
+ );
});
});
describe('with a closed merge request', () => {
beforeEach(() => {
- vm = mountComponent(Component, { mr: {
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
- sourceBranchRemoved: false,
- targetBranchPath: 'foo/bar/commits-path',
- targetBranchTreePath: 'foo/bar/tree/path',
- targetBranch: 'master',
- isOpen: false,
- emailPatchesPath: '/mr/email-patches',
- plainDiffPath: '/mr/plainDiffPath',
- } });
+ vm = mountComponent(Component, {
+ mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: false,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ statusPath: 'abc',
+ },
+ });
});
it('does not render checkout branch button with modal trigger', () => {
@@ -165,30 +193,29 @@ describe('MRWidgetHeader', () => {
});
it('does not render download dropdown with links', () => {
- expect(
- vm.$el.querySelector('.js-download-email-patches'),
- ).toEqual(null);
+ expect(vm.$el.querySelector('.js-download-email-patches')).toEqual(null);
- expect(
- vm.$el.querySelector('.js-download-plain-diff'),
- ).toEqual(null);
+ expect(vm.$el.querySelector('.js-download-plain-diff')).toEqual(null);
});
});
describe('without diverged commits', () => {
beforeEach(() => {
- vm = mountComponent(Component, { mr: {
- divergedCommitsCount: 0,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
- sourceBranchRemoved: false,
- targetBranchPath: 'foo/bar/commits-path',
- targetBranchTreePath: 'foo/bar/tree/path',
- targetBranch: 'master',
- isOpen: true,
- emailPatchesPath: '/mr/email-patches',
- plainDiffPath: '/mr/plainDiffPath',
- } });
+ vm = mountComponent(Component, {
+ mr: {
+ divergedCommitsCount: 0,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ statusPath: 'abc',
+ },
+ });
});
it('does not render diverged commits info', () => {
@@ -198,22 +225,27 @@ describe('MRWidgetHeader', () => {
describe('with diverged commits', () => {
beforeEach(() => {
- vm = mountComponent(Component, { mr: {
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
- sourceBranchRemoved: false,
- targetBranchPath: 'foo/bar/commits-path',
- targetBranchTreePath: 'foo/bar/tree/path',
- targetBranch: 'master',
- isOpen: true,
- emailPatchesPath: '/mr/email-patches',
- plainDiffPath: '/mr/plainDiffPath',
- } });
+ vm = mountComponent(Component, {
+ mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ statusPath: 'abc',
+ },
+ });
});
it('renders diverged commits info', () => {
- expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual('(12 commits behind)');
+ expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual(
+ '(12 commits behind)',
+ );
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 3dd75307484..3fc7663b9c2 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -1,213 +1,218 @@
-/* eslint-disable */
-
export default {
- "id": 132,
- "iid": 22,
- "assignee_id": null,
- "author_id": 1,
- "description": "",
- "lock_version": null,
- "milestone_id": null,
- "position": 0,
- "state": "merged",
- "title": "Update README.md",
- "updated_by_id": null,
- "created_at": "2017-04-07T12:27:26.718Z",
- "updated_at": "2017-04-07T15:39:25.852Z",
- "time_estimate": 0,
- "total_time_spent": 0,
- "human_time_estimate": null,
- "human_total_time_spent": null,
- "in_progress_merge_commit_sha": null,
- "merge_commit_sha": "53027d060246c8f47e4a9310fb332aa52f221775",
- "merge_error": null,
- "merge_params": {
- "force_remove_source_branch": null
+ id: 132,
+ iid: 22,
+ assignee_id: null,
+ author_id: 1,
+ description: '',
+ lock_version: null,
+ milestone_id: null,
+ position: 0,
+ state: 'merged',
+ title: 'Update README.md',
+ updated_by_id: null,
+ created_at: '2017-04-07T12:27:26.718Z',
+ updated_at: '2017-04-07T15:39:25.852Z',
+ time_estimate: 0,
+ total_time_spent: 0,
+ human_time_estimate: null,
+ human_total_time_spent: null,
+ in_progress_merge_commit_sha: null,
+ merge_commit_sha: '53027d060246c8f47e4a9310fb332aa52f221775',
+ merge_error: null,
+ merge_params: {
+ force_remove_source_branch: null,
},
- "merge_status": "can_be_merged",
- "merge_user_id": null,
- "merge_when_pipeline_succeeds": false,
- "source_branch": "daaaa",
- "source_branch_link": "daaaa",
- "source_project_id": 19,
- "target_branch": "master",
- "target_project_id": 19,
- "metrics": {
- "merged_by": {
- "name": "Administrator",
- "username": "root",
- "id": 1,
- "state": "active",
- "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
- "web_url": "http://localhost:3000/root"
+ merge_status: 'can_be_merged',
+ merge_user_id: null,
+ merge_when_pipeline_succeeds: false,
+ source_branch: 'daaaa',
+ source_branch_link: 'daaaa',
+ source_project_id: 19,
+ target_branch: 'master',
+ target_project_id: 19,
+ metrics: {
+ merged_by: {
+ name: 'Administrator',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://localhost:3000/root',
},
- "merged_at": "2017-04-07T15:39:25.696Z",
- "closed_by": null,
- "closed_at": null
+ merged_at: '2017-04-07T15:39:25.696Z',
+ closed_by: null,
+ closed_at: null,
},
- "author": {
- "name": "Administrator",
- "username": "root",
- "id": 1,
- "state": "active",
- "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
- "web_url": "http://localhost:3000/root"
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://localhost:3000/root',
},
- "merge_user": null,
- "diff_head_sha": "104096c51715e12e7ae41f9333e9fa35b73f385d",
- "diff_head_commit_short_id": "104096c5",
- "merge_commit_message": "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
- "pipeline": {
- "id": 172,
- "user": {
- "name": "Administrator",
- "username": "root",
- "id": 1,
- "state": "active",
- "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
- "web_url": "http://localhost:3000/root"
+ merge_user: null,
+ diff_head_sha: '104096c51715e12e7ae41f9333e9fa35b73f385d',
+ diff_head_commit_short_id: '104096c5',
+ merge_commit_message:
+ "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
+ pipeline: {
+ id: 172,
+ user: {
+ name: 'Administrator',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://localhost:3000/root',
},
- "active": false,
- "coverage": "92.16",
- "path": "/root/acets-app/pipelines/172",
- "details": {
- "status": {
- "icon": "icon_status_success",
- "favicon": "favicon_status_success",
- "text": "passed",
- "label": "passed",
- "group": "success",
- "has_details": true,
- "details_path": "/root/acets-app/pipelines/172"
+ active: false,
+ coverage: '92.16',
+ path: '/root/acets-app/pipelines/172',
+ details: {
+ status: {
+ icon: 'icon_status_success',
+ favicon: 'favicon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/acets-app/pipelines/172',
},
- "duration": null,
- "finished_at": "2017-04-07T14:00:14.256Z",
- "stages": [
+ duration: null,
+ finished_at: '2017-04-07T14:00:14.256Z',
+ stages: [
{
- "name": "build",
- "title": "build: failed",
- "status": {
- "icon": "icon_status_failed",
- "favicon": "favicon_status_failed",
- "text": "failed",
- "label": "failed",
- "group": "failed",
- "has_details": true,
- "details_path": "/root/acets-app/pipelines/172#build"
+ name: 'build',
+ title: 'build: failed',
+ status: {
+ icon: 'icon_status_failed',
+ favicon: 'favicon_status_failed',
+ text: 'failed',
+ label: 'failed',
+ group: 'failed',
+ has_details: true,
+ details_path: '/root/acets-app/pipelines/172#build',
},
- "path": "/root/acets-app/pipelines/172#build",
- "dropdown_path": "/root/acets-app/pipelines/172/stage.json?stage=build"
+ path: '/root/acets-app/pipelines/172#build',
+ dropdown_path: '/root/acets-app/pipelines/172/stage.json?stage=build',
},
{
- "name": "review",
- "title": "review: skipped",
- "status": {
- "icon": "icon_status_skipped",
- "favicon": "favicon_status_skipped",
- "text": "skipped",
- "label": "skipped",
- "group": "skipped",
- "has_details": true,
- "details_path": "/root/acets-app/pipelines/172#review"
+ name: 'review',
+ title: 'review: skipped',
+ status: {
+ icon: 'icon_status_skipped',
+ favicon: 'favicon_status_skipped',
+ text: 'skipped',
+ label: 'skipped',
+ group: 'skipped',
+ has_details: true,
+ details_path: '/root/acets-app/pipelines/172#review',
},
- "path": "/root/acets-app/pipelines/172#review",
- "dropdown_path": "/root/acets-app/pipelines/172/stage.json?stage=review"
- }
- ],
- "artifacts": [
-
+ path: '/root/acets-app/pipelines/172#review',
+ dropdown_path: '/root/acets-app/pipelines/172/stage.json?stage=review',
+ },
],
- "manual_actions": [
+ artifacts: [],
+ manual_actions: [
{
- "name": "stop_review",
- "path": "/root/acets-app/builds/1427/play",
- "playable": false
- }
- ]
+ name: 'stop_review',
+ path: '/root/acets-app/builds/1427/play',
+ playable: false,
+ },
+ ],
},
- "flags": {
- "latest": false,
- "triggered": false,
- "stuck": false,
- "yaml_errors": false,
- "retryable": true,
- "cancelable": false
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: false,
},
- "ref": {
- "name": "daaaa",
- "path": "/root/acets-app/tree/daaaa",
- "tag": false,
- "branch": true
+ ref: {
+ name: 'daaaa',
+ path: '/root/acets-app/tree/daaaa',
+ tag: false,
+ branch: true,
},
- "commit": {
- "id": "104096c51715e12e7ae41f9333e9fa35b73f385d",
- "short_id": "104096c5",
- "title": "Update README.md",
- "created_at": "2017-04-07T15:27:18.000+03:00",
- "parent_ids": [
- "2396536178668d8930c29d904e53bd4d06228b32"
- ],
- "message": "Update README.md",
- "author_name": "Administrator",
- "author_email": "admin@example.com",
- "authored_date": "2017-04-07T15:27:18.000+03:00",
- "committer_name": "Administrator",
- "committer_email": "admin@example.com",
- "committed_date": "2017-04-07T15:27:18.000+03:00",
- "author": {
- "name": "Administrator",
- "username": "root",
- "id": 1,
- "state": "active",
- "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
- "web_url": "http://localhost:3000/root"
+ commit: {
+ id: '104096c51715e12e7ae41f9333e9fa35b73f385d',
+ short_id: '104096c5',
+ title: 'Update README.md',
+ created_at: '2017-04-07T15:27:18.000+03:00',
+ parent_ids: ['2396536178668d8930c29d904e53bd4d06228b32'],
+ message: 'Update README.md',
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2017-04-07T15:27:18.000+03:00',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2017-04-07T15:27:18.000+03:00',
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://localhost:3000/root',
},
- "author_gravatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
- "commit_url": "http://localhost:3000/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d",
- "commit_path": "/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d"
+ author_gravatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d',
+ commit_path: '/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d',
},
- "retry_path": "/root/acets-app/pipelines/172/retry",
- "created_at": "2017-04-07T12:27:19.520Z",
- "updated_at": "2017-04-07T15:28:44.800Z"
+ retry_path: '/root/acets-app/pipelines/172/retry',
+ created_at: '2017-04-07T12:27:19.520Z',
+ updated_at: '2017-04-07T15:28:44.800Z',
},
- "work_in_progress": false,
- "source_branch_exists": false,
- "mergeable_discussions_state": true,
- "conflicts_can_be_resolved_in_ui": false,
- "branch_missing": true,
- "commits_count": 1,
- "has_conflicts": false,
- "can_be_merged": true,
- "has_ci": true,
- "ci_status": "success",
- "pipeline_status_path": "/root/acets-app/merge_requests/22/pipeline_status",
- "issues_links": {
- "closing": "",
- "mentioned_but_not_closing": ""
+ work_in_progress: false,
+ source_branch_exists: false,
+ mergeable_discussions_state: true,
+ conflicts_can_be_resolved_in_ui: false,
+ branch_missing: true,
+ commits_count: 1,
+ has_conflicts: false,
+ can_be_merged: true,
+ has_ci: true,
+ ci_status: 'success',
+ pipeline_status_path: '/root/acets-app/merge_requests/22/pipeline_status',
+ issues_links: {
+ closing: '',
+ mentioned_but_not_closing: '',
},
- "current_user": {
- "can_resolve_conflicts": true,
- "can_remove_source_branch": false,
- "can_revert_on_current_merge_request": true,
- "can_cherry_pick_on_current_merge_request": true
+ current_user: {
+ can_resolve_conflicts: true,
+ can_remove_source_branch: false,
+ can_revert_on_current_merge_request: true,
+ can_cherry_pick_on_current_merge_request: true,
},
- "target_branch_path": "/root/acets-app/branches/master",
- "source_branch_path": "/root/acets-app/branches/daaaa",
- "conflict_resolution_ui_path": "/root/acets-app/merge_requests/22/conflicts",
- "remove_wip_path": "/root/acets-app/merge_requests/22/remove_wip",
- "cancel_merge_when_pipeline_succeeds_path": "/root/acets-app/merge_requests/22/cancel_merge_when_pipeline_succeeds",
- "create_issue_to_resolve_discussions_path": "/root/acets-app/issues/new?merge_request_to_resolve_discussions_of=22",
- "merge_path": "/root/acets-app/merge_requests/22/merge",
- "cherry_pick_in_fork_path": "/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+revert+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1",
- "revert_in_fork_path": "/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1",
- "email_patches_path": "/root/acets-app/merge_requests/22.patch",
- "plain_diff_path": "/root/acets-app/merge_requests/22.diff",
- "status_path": "/root/acets-app/merge_requests/22.json",
- "merge_check_path": "/root/acets-app/merge_requests/22/merge_check",
- "ci_environments_status_url": "/root/acets-app/merge_requests/22/ci_environments_status",
- "project_archived": false,
- "merge_commit_message_with_description": "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
- "diverged_commits_count": 0,
- "only_allow_merge_if_pipeline_succeeds": false,
- "commit_change_content_path": "/root/acets-app/merge_requests/22/commit_change_content"
-}
+ target_branch_path: '/root/acets-app/branches/master',
+ source_branch_path: '/root/acets-app/branches/daaaa',
+ conflict_resolution_ui_path: '/root/acets-app/merge_requests/22/conflicts',
+ remove_wip_path: '/root/acets-app/merge_requests/22/remove_wip',
+ cancel_merge_when_pipeline_succeeds_path:
+ '/root/acets-app/merge_requests/22/cancel_merge_when_pipeline_succeeds',
+ create_issue_to_resolve_discussions_path:
+ '/root/acets-app/issues/new?merge_request_to_resolve_discussions_of=22',
+ merge_path: '/root/acets-app/merge_requests/22/merge',
+ cherry_pick_in_fork_path:
+ '/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+revert+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
+ revert_in_fork_path:
+ '/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
+ email_patches_path: '/root/acets-app/merge_requests/22.patch',
+ plain_diff_path: '/root/acets-app/merge_requests/22.diff',
+ status_path: '/root/acets-app/merge_requests/22.json',
+ merge_check_path: '/root/acets-app/merge_requests/22/merge_check',
+ ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status',
+ project_archived: false,
+ merge_commit_message_with_description:
+ "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
+ diverged_commits_count: 0,
+ only_allow_merge_if_pipeline_succeeds: false,
+ commit_change_content_path: '/root/acets-app/merge_requests/22/commit_change_content',
+};
diff --git a/spec/javascripts/vue_shared/components/mock_data.js b/spec/javascripts/vue_shared/components/mock_data.js
index 0d781bdca74..15b56c58c33 100644
--- a/spec/javascripts/vue_shared/components/mock_data.js
+++ b/spec/javascripts/vue_shared/components/mock_data.js
@@ -1,5 +1,3 @@
-/* eslint-disable */
-
export const mockMetrics = [
[1493716685, '4.30859375'],
[1493716745, '4.30859375'],
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
new file mode 100644
index 00000000000..14d055cbcc1
--- /dev/null
+++ b/spec/lib/backup/files_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Backup::Files do
+ let(:progress) { StringIO.new }
+ let!(:project) { create(:project) }
+
+ before do
+ allow(progress).to receive(:puts)
+ allow(progress).to receive(:print)
+ allow(FileUtils).to receive(:mkdir_p).and_return(true)
+ allow(FileUtils).to receive(:mv).and_return(true)
+ allow(File).to receive(:exist?).and_return(true)
+ allow(File).to receive(:realpath).with("/var/gitlab-registry").and_return("/var/gitlab-registry")
+ allow(File).to receive(:realpath).with("/var/gitlab-registry/..").and_return("/var")
+
+ allow_any_instance_of(String).to receive(:color) do |string, _color|
+ string
+ end
+
+ allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
+ end
+
+ describe '#restore' do
+ subject { described_class.new('registry', '/var/gitlab-registry') }
+ let(:timestamp) { Time.utc(2017, 3, 22) }
+
+ around do |example|
+ Timecop.freeze(timestamp) { example.run }
+ end
+
+ describe 'folders with permission' do
+ before do
+ allow(subject).to receive(:run_pipeline!).and_return(true)
+ allow(subject).to receive(:backup_existing_files).and_return(true)
+ allow(Dir).to receive(:glob).with("/var/gitlab-registry/*", File::FNM_DOTMATCH).and_return(["/var/gitlab-registry/.", "/var/gitlab-registry/..", "/var/gitlab-registry/sample1"])
+ end
+
+ it 'moves all necessary files' do
+ allow(subject).to receive(:backup_existing_files).and_call_original
+ expect(FileUtils).to receive(:mv).with(["/var/gitlab-registry/sample1"], File.join(Gitlab.config.backup.path, "tmp", "registry.#{Time.now.to_i}"))
+ subject.restore
+ end
+
+ it 'raises no errors' do
+ expect { subject.restore }.not_to raise_error
+ end
+
+ it 'calls tar command with unlink' do
+ expect(subject).to receive(:run_pipeline!).with([%w(gzip -cd), %w(tar --unlink-first --recursive-unlink -C /var/gitlab-registry -xf -)], any_args)
+ subject.restore
+ end
+ end
+
+ describe 'folders without permissions' do
+ before do
+ allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
+ allow(subject).to receive(:run_pipeline!).and_return(true)
+ end
+
+ it 'shows error message' do
+ expect(subject).to receive(:access_denied_error).with("/var/gitlab-registry")
+ subject.restore
+ end
+ end
+ end
+end
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index 03573c304aa..e4c1c9bafc0 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -7,6 +7,8 @@ describe Backup::Repository do
before do
allow(progress).to receive(:puts)
allow(progress).to receive(:print)
+ allow(FileUtils).to receive(:mkdir_p).and_return(true)
+ allow(FileUtils).to receive(:mv).and_return(true)
allow_any_instance_of(String).to receive(:color) do |string, _color|
string
@@ -68,6 +70,17 @@ describe Backup::Repository do
end
end
end
+
+ describe 'folders without permissions' do
+ before do
+ allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
+ end
+
+ it 'shows error message' do
+ expect(subject).to receive(:access_denied_error)
+ subject.restore
+ end
+ end
end
describe '#empty_repo?' do
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index cbb0089bde7..a50329473ad 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -167,6 +167,15 @@ describe Banzai::Filter::AutolinkFilter do
expect(actual).to eq(expected_complicated_link)
end
+ it 'does not double-encode HTML entities' do
+ encoded_link = "#{link}?foo=bar&amp;baz=quux"
+ expected_encoded_link = %Q{<a href="#{encoded_link}">#{encoded_link}</a>}
+ actual = unescape(filter(encoded_link).to_html)
+
+ expect(actual).to eq(Rinku.auto_link(encoded_link))
+ expect(actual).to eq(expected_encoded_link)
+ end
+
it 'does not include trailing HTML entities' do
doc = filter("See &lt;&lt;&lt;#{link}&gt;&gt;&gt;")
diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
index e112e9e9e3d..5ce84c61042 100644
--- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
@@ -51,4 +51,20 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201
expect { described_class.new.perform(1, 6) }
.to raise_error ActiveRecord::RecordNotUnique
end
+
+ context 'when invalid class can be loaded due to single table inheritance' do
+ let(:commit_status) do
+ jobs.create!(id: 7, commit_id: 1, project_id: 123, stage_idx: 4,
+ stage: 'post-deploy', status: :failed)
+ end
+
+ before do
+ commit_status.update_column(:type, 'SomeClass')
+ end
+
+ it 'does ignore single table inheritance type' do
+ expect { described_class.new.perform(1, 7) }.not_to raise_error
+ expect(jobs.find(7)).to have_attributes(stage_id: (a_value > 0))
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
new file mode 100644
index 00000000000..2ce858836e3
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Policy::Variables do
+ set(:project) { create(:project) }
+
+ let(:pipeline) do
+ build(:ci_empty_pipeline, project: project, ref: 'master', source: :push)
+ end
+
+ let(:ci_build) do
+ build(:ci_build, pipeline: pipeline, project: project, ref: 'master')
+ end
+
+ let(:seed) { double('build seed', to_resource: ci_build) }
+
+ before do
+ pipeline.variables.build(key: 'CI_PROJECT_NAME', value: '')
+ end
+
+ describe '#satisfied_by?' do
+ it 'is satisfied by at least one matching statement' do
+ policy = described_class.new(['$CI_PROJECT_ID', '$UNDEFINED'])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied by an overriden empty variable' do
+ policy = described_class.new(['$CI_PROJECT_NAME'])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is satisfied by a truthy pipeline expression' do
+ policy = described_class.new([%($CI_PIPELINE_SOURCE == "push")])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied by a falsy pipeline expression' do
+ policy = described_class.new([%($CI_PIPELINE_SOURCE == "invalid source")])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is satisfied by a truthy expression using undefined variable' do
+ policy = described_class.new(['$UNDEFINED == null'])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied by a falsy expression using undefined variable' do
+ policy = described_class.new(['$UNDEFINED'])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'allows to evaluate regular secret variables' do
+ create(:ci_variable, project: project, key: 'SECRET', value: 'my secret')
+
+ policy = described_class.new(["$SECRET == 'my secret'"])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'does not persist neither pipeline nor build' do
+ described_class.new('$VAR').satisfied_by?(pipeline, seed)
+
+ expect(pipeline).not_to be_persisted
+ expect(seed.to_resource).not_to be_persisted
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb
index 5a21282712a..cce4efaa069 100644
--- a/spec/lib/gitlab/ci/build/step_spec.rb
+++ b/spec/lib/gitlab/ci/build/step_spec.rb
@@ -5,10 +5,14 @@ describe Gitlab::Ci::Build::Step do
shared_examples 'has correct script' do
subject { described_class.from_commands(job) }
+ before do
+ job.run!
+ end
+
it 'fabricates an object' do
expect(subject.name).to eq(:script)
expect(subject.script).to eq(script)
- expect(subject.timeout).to eq(job.timeout)
+ expect(subject.timeout).to eq(job.metadata_timeout)
expect(subject.when).to eq('on_success')
expect(subject.allow_failure).to be_falsey
end
@@ -47,6 +51,10 @@ describe Gitlab::Ci::Build::Step do
subject { described_class.from_after_script(job) }
+ before do
+ job.run!
+ end
+
context 'when after_script is empty' do
it 'doesn not fabricate an object' do
is_expected.to be_nil
@@ -59,7 +67,7 @@ describe Gitlab::Ci::Build::Step do
it 'fabricates an object' do
expect(subject.name).to eq(:after_script)
expect(subject.script).to eq(['ls -la', 'date'])
- expect(subject.timeout).to eq(job.timeout)
+ expect(subject.timeout).to eq(job.metadata_timeout)
expect(subject.when).to eq('always')
expect(subject.allow_failure).to be_truthy
end
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index 5e83abf645b..08718c382b9 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -83,6 +83,39 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
end
+ context 'when specifying valid variables expressions policy' do
+ let(:config) { { variables: ['$VAR == null'] } }
+
+ it 'is a correct configuraton' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq(config)
+ end
+ end
+
+ context 'when specifying variables expressions in invalid format' do
+ let(:config) { { variables: '$MY_VAR' } }
+
+ it 'reports an error about invalid format' do
+ expect(entry.errors).to include /should be an array of strings/
+ end
+ end
+
+ context 'when specifying invalid variables expressions statement' do
+ let(:config) { { variables: ['$MY_VAR =='] } }
+
+ it 'reports an error about invalid statement' do
+ expect(entry.errors).to include /invalid expression syntax/
+ end
+ end
+
+ context 'when specifying invalid variables expressions token' do
+ let(:config) { { variables: ['$MY_VAR == 123'] } }
+
+ it 'reports an error about invalid statement' do
+ expect(entry.errors).to include /invalid expression syntax/
+ end
+ end
+
context 'when specifying unknown policy' do
let(:config) { { refs: ['master'], invalid: :something } }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 2258ae83f38..8312fa47cfa 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -6,7 +6,8 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
let(:pipeline) do
build(:ci_pipeline_with_one_job, project: project,
- ref: 'master')
+ ref: 'master',
+ user: user)
end
let(:command) do
@@ -42,6 +43,10 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
expect(pipeline.stages.first.builds).to be_one
expect(pipeline.stages.first.builds.first).not_to be_persisted
end
+
+ it 'correctly assigns user' do
+ expect(pipeline.builds).to all(have_attributes(user: user))
+ end
end
context 'when pipeline is empty' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
index 86234dfb9e5..1ccb792d1da 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
@@ -73,6 +73,22 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
expect(token).not_to be_nil
expect(token.build.evaluate).to eq 'some " string'
end
+
+ it 'allows to use an empty string inside single quotes' do
+ scanner = StringScanner.new(%(''))
+
+ token = described_class.scan(scanner)
+
+ expect(token.build.evaluate).to eq ''
+ end
+
+ it 'allow to use an empty string inside double quotes' do
+ scanner = StringScanner.new(%(""))
+
+ token = described_class.scan(scanner)
+
+ expect(token.build.evaluate).to eq ''
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index 472a58599d8..6685bf5385b 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,14 +1,23 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Statement do
- let(:pipeline) { build(:ci_pipeline) }
-
subject do
- described_class.new(text, pipeline)
+ described_class.new(text, variables)
+ end
+
+ let(:variables) do
+ { 'PRESENT_VARIABLE' => 'my variable',
+ EMPTY_VARIABLE: '' }
end
- before do
- pipeline.variables.build([key: 'VARIABLE', value: 'my variable'])
+ describe '.new' do
+ context 'when variables are not provided' do
+ it 'allows to properly initializes the statement' do
+ statement = described_class.new('$PRESENT_VARIABLE')
+
+ expect(statement.evaluate).to be_nil
+ end
+ end
end
describe '#parse_tree' do
@@ -23,18 +32,26 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
context 'when expression grammar is incorrect' do
table = [
- '$VAR "text"', # missing operator
- '== "123"', # invalid right side
- "'single quotes'", # single quotes string
- '$VAR ==', # invalid right side
- '12345', # unknown syntax
- '' # empty statement
+ '$VAR "text"', # missing operator
+ '== "123"', # invalid left side
+ '"some string"', # only string provided
+ '$VAR ==', # invalid right side
+ '12345', # unknown syntax
+ '' # empty statement
]
table.each do |syntax|
- it "raises an error when syntax is `#{syntax}`" do
- expect { described_class.new(syntax, pipeline).parse_tree }
- .to raise_error described_class::StatementError
+ context "when expression grammar is #{syntax.inspect}" do
+ let(:text) { syntax }
+
+ it 'aises a statement error exception' do
+ expect { subject.parse_tree }
+ .to raise_error described_class::StatementError
+ end
+
+ it 'is an invalid statement' do
+ expect(subject).not_to be_valid
+ end
end
end
end
@@ -47,10 +64,14 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
expect(subject.parse_tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
end
+
+ it 'is a valid statement' do
+ expect(subject).to be_valid
+ end
end
context 'when using a single token' do
- let(:text) { '$VARIABLE' }
+ let(:text) { '$PRESENT_VARIABLE' }
it 'returns a single token instance' do
expect(subject.parse_tree)
@@ -62,14 +83,17 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
describe '#evaluate' do
statements = [
- ['$VARIABLE == "my variable"', true],
- ["$VARIABLE == 'my variable'", true],
- ['"my variable" == $VARIABLE', true],
- ['$VARIABLE == null', false],
- ['$VAR == null', true],
- ['null == $VAR', true],
- ['$VARIABLE', 'my variable'],
- ['$VAR', nil]
+ ['$PRESENT_VARIABLE == "my variable"', true],
+ ["$PRESENT_VARIABLE == 'my variable'", true],
+ ['"my variable" == $PRESENT_VARIABLE', true],
+ ['$PRESENT_VARIABLE == null', false],
+ ['$EMPTY_VARIABLE == null', false],
+ ['"" == $EMPTY_VARIABLE', true],
+ ['$EMPTY_VARIABLE', ''],
+ ['$UNDEFINED_VARIABLE == null', true],
+ ['null == $UNDEFINED_VARIABLE', true],
+ ['$PRESENT_VARIABLE', 'my variable'],
+ ['$UNDEFINED_VARIABLE', nil]
]
statements.each do |expression, value|
@@ -82,4 +106,25 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
end
end
end
+
+ describe '#truthful?' do
+ statements = [
+ ['$PRESENT_VARIABLE == "my variable"', true],
+ ["$PRESENT_VARIABLE == 'no match'", false],
+ ['$UNDEFINED_VARIABLE == null', true],
+ ['$PRESENT_VARIABLE', true],
+ ['$UNDEFINED_VARIABLE', false],
+ ['$EMPTY_VARIABLE', false]
+ ]
+
+ statements.each do |expression, value|
+ context "when using expression `#{expression}`" do
+ let(:text) { expression }
+
+ it "returns `#{value.inspect}`" do
+ expect(subject.truthful?).to eq value
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 116573379e0..fffa727c2ed 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -21,16 +21,6 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
end
end
- describe '#user=' do
- let(:user) { build(:user) }
-
- it 'assignes user to a build' do
- subject.user = user
-
- expect(subject.attributes).to include(user: user)
- end
- end
-
describe '#to_resource' do
it 'returns a valid build resource' do
expect(subject.to_resource).to be_a(::Ci::Build)
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index 8f0bf40d624..eb1b285c7bd 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -95,16 +95,6 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do
end
end
- describe '#user=' do
- let(:user) { build(:user) }
-
- it 'assignes relevant pipeline attributes' do
- subject.user = user
-
- expect(subject.seeds.map(&:attributes)).to all(include(user: user))
- end
- end
-
describe '#to_resource' do
it 'builds a valid stage object with all builds' do
subject.to_resource.save!
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index cc1257484d2..bf9208f1ff4 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -46,9 +46,13 @@ describe Gitlab::Ci::Variables::Collection::Item do
end
end
- describe '#to_hash' do
- it 'returns a hash representation of a collection item' do
- expect(described_class.new(**variable).to_hash).to eq variable
+ describe '#to_runner_variable' do
+ it 'returns a runner-compatible hash representation' do
+ runner_variable = described_class
+ .new(**variable)
+ .to_runner_variable
+
+ expect(runner_variable).to eq variable
end
end
end
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index 90b6e178242..cb2f7718c9c 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -7,7 +7,7 @@ describe Gitlab::Ci::Variables::Collection do
collection = described_class.new([variable])
- expect(collection.first.to_hash).to eq variable
+ expect(collection.first.to_runner_variable).to eq variable
end
it 'can be initialized without an argument' do
@@ -96,4 +96,19 @@ describe Gitlab::Ci::Variables::Collection do
.to eq [{ key: 'TEST', value: 1, public: true }]
end
end
+
+ describe '#to_hash' do
+ it 'returns regular hash in valid order without duplicates' do
+ collection = described_class.new
+ .append(key: 'TEST1', value: 'test-1')
+ .append(key: 'TEST2', value: 'test-2')
+ .append(key: 'TEST1', value: 'test-3')
+
+ expect(collection.to_hash).to eq('TEST1' => 'test-3',
+ 'TEST2' => 'test-2')
+
+ expect(collection.to_hash).to include(TEST1: 'test-3')
+ expect(collection.to_hash).not_to include(TEST1: 'test-1')
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index fbc2af29b98..ecb16daec96 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1311,6 +1311,14 @@ module Gitlab
Gitlab::Ci::YamlProcessor.new(config)
end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings")
end
+
+ it 'returns errors if pipeline variables expression is invalid' do
+ config = YAML.dump({ rspec: { script: 'test', only: { variables: ['== null'] } } })
+
+ expect { Gitlab::Ci::YamlProcessor.new(config) }
+ .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
+ 'jobs:rspec:only variables invalid expression syntax')
+ end
end
describe "Validate configuration templates" do
diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
index 143aa2218c9..6fd2b33486b 100644
--- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb
+++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::Git::GitmodulesParser do
it 'should parse a .gitmodules file correctly' do
- parser = described_class.new(<<-'GITMODULES'.strip_heredoc)
+ data = <<~GITMODULES
[submodule "vendor/libgit2"]
path = vendor/libgit2
[submodule "vendor/libgit2"]
@@ -16,6 +16,7 @@ describe Gitlab::Git::GitmodulesParser do
url = https://example.com/another/project
GITMODULES
+ parser = described_class.new(data.gsub("\n", "\r\n"))
modules = parser.parse
expect(modules).to eq({
diff --git a/spec/lib/gitlab/git/env_spec.rb b/spec/lib/gitlab/git/hook_env_spec.rb
index 03836d49518..e6aa5ad8c90 100644
--- a/spec/lib/gitlab/git/env_spec.rb
+++ b/spec/lib/gitlab/git/hook_env_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
-describe Gitlab::Git::Env do
+describe Gitlab::Git::HookEnv do
+ let(:gl_repository) { 'project-123' }
+
describe ".set" do
context 'with RequestStore.store disabled' do
before do
@@ -8,9 +10,9 @@ describe Gitlab::Git::Env do
end
it 'does not store anything' do
- described_class.set(GIT_OBJECT_DIRECTORY: 'foo')
+ described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
- expect(described_class.all).to be_empty
+ expect(described_class.all(gl_repository)).to be_empty
end
end
@@ -21,15 +23,19 @@ describe Gitlab::Git::Env do
it 'whitelist some `GIT_*` variables and stores them using RequestStore' do
described_class.set(
- GIT_OBJECT_DIRECTORY: 'foo',
- GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar',
+ gl_repository,
+ GIT_OBJECT_DIRECTORY_RELATIVE: 'foo',
+ GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: 'bar',
GIT_EXEC_PATH: 'baz',
PATH: '~/.bin:/bin')
- expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo')
- expect(described_class[:GIT_ALTERNATE_OBJECT_DIRECTORIES]).to eq('bar')
- expect(described_class[:GIT_EXEC_PATH]).to be_nil
- expect(described_class[:bar]).to be_nil
+ git_env = described_class.all(gl_repository)
+
+ expect(git_env[:GIT_OBJECT_DIRECTORY_RELATIVE]).to eq('foo')
+ expect(git_env[:GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE]).to eq('bar')
+ expect(git_env[:GIT_EXEC_PATH]).to be_nil
+ expect(git_env[:PATH]).to be_nil
+ expect(git_env[:bar]).to be_nil
end
end
end
@@ -39,14 +45,15 @@ describe Gitlab::Git::Env do
before do
allow(RequestStore).to receive(:active?).and_return(true)
described_class.set(
- GIT_OBJECT_DIRECTORY: 'foo',
- GIT_ALTERNATE_OBJECT_DIRECTORIES: ['bar'])
+ gl_repository,
+ GIT_OBJECT_DIRECTORY_RELATIVE: 'foo',
+ GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: ['bar'])
end
it 'returns an env hash' do
- expect(described_class.all).to eq({
- 'GIT_OBJECT_DIRECTORY' => 'foo',
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => ['bar']
+ expect(described_class.all(gl_repository)).to eq({
+ 'GIT_OBJECT_DIRECTORY_RELATIVE' => 'foo',
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['bar']
})
end
end
@@ -56,8 +63,8 @@ describe Gitlab::Git::Env do
context 'with RequestStore.store enabled' do
using RSpec::Parameterized::TableSyntax
- let(:key) { 'GIT_OBJECT_DIRECTORY' }
- subject { described_class.to_env_hash }
+ let(:key) { 'GIT_OBJECT_DIRECTORY_RELATIVE' }
+ subject { described_class.to_env_hash(gl_repository) }
where(:input, :output) do
nil | nil
@@ -70,7 +77,7 @@ describe Gitlab::Git::Env do
with_them do
before do
allow(RequestStore).to receive(:active?).and_return(true)
- described_class.set(key.to_sym => input)
+ described_class.set(gl_repository, key.to_sym => input)
end
it 'puts the right value in the hash' do
@@ -84,47 +91,25 @@ describe Gitlab::Git::Env do
end
end
- describe ".[]" do
- context 'with RequestStore.store enabled' do
- before do
- allow(RequestStore).to receive(:active?).and_return(true)
- end
-
- before do
- described_class.set(
- GIT_OBJECT_DIRECTORY: 'foo',
- GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar')
- end
-
- it 'returns a stored value for an existing key' do
- expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo')
- end
-
- it 'returns nil for an non-existing key' do
- expect(described_class[:foo]).to be_nil
- end
- end
- end
-
describe 'thread-safety' do
context 'with RequestStore.store enabled' do
before do
allow(RequestStore).to receive(:active?).and_return(true)
- described_class.set(GIT_OBJECT_DIRECTORY: 'foo')
+ described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'foo')
end
it 'is thread-safe' do
another_thread = Thread.new do
- described_class.set(GIT_OBJECT_DIRECTORY: 'bar')
+ described_class.set(gl_repository, GIT_OBJECT_DIRECTORY_RELATIVE: 'bar')
Thread.stop
- described_class[:GIT_OBJECT_DIRECTORY]
+ described_class.all(gl_repository)[:GIT_OBJECT_DIRECTORY_RELATIVE]
end
# Ensure another_thread runs first
sleep 0.1 until another_thread.stop?
- expect(described_class[:GIT_OBJECT_DIRECTORY]).to eq('foo')
+ expect(described_class.all(gl_repository)[:GIT_OBJECT_DIRECTORY_RELATIVE]).to eq('foo')
another_thread.run
expect(another_thread.value).to eq('bar')
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 0e315b3f49e..5cbe2808d0b 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -120,7 +120,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe 'alternates keyword argument' do
context 'with no Git env stored' do
before do
- allow(Gitlab::Git::Env).to receive(:all).and_return({})
+ allow(Gitlab::Git::HookEnv).to receive(:all).and_return({})
end
it "is passed an empty array" do
@@ -132,7 +132,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'with absolute and relative Git object dir envvars stored' do
before do
- allow(Gitlab::Git::Env).to receive(:all).and_return({
+ allow(Gitlab::Git::HookEnv).to receive(:all).and_return({
'GIT_OBJECT_DIRECTORY_RELATIVE' => './objects/foo',
'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['./objects/bar', './objects/baz'],
'GIT_OBJECT_DIRECTORY' => 'ignored',
@@ -148,22 +148,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.rugged
end
end
-
- context 'with only absolute Git object dir envvars stored' do
- before do
- allow(Gitlab::Git::Env).to receive(:all).and_return({
- 'GIT_OBJECT_DIRECTORY' => 'foo',
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => %w[bar baz],
- 'GIT_OTHER' => 'another_env'
- })
- end
-
- it "is passed the absolute object dir envvars as is" do
- expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: %w[foo bar baz])
-
- repository.rugged
- end
- end
end
end
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index 4e0ee206219..32ec1e029c8 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -3,17 +3,6 @@ require 'spec_helper'
describe Gitlab::Git::RevList do
let(:repository) { create(:project, :repository).repository.raw }
let(:rev_list) { described_class.new(repository, newrev: 'newrev') }
- let(:env_hash) do
- {
- 'GIT_OBJECT_DIRECTORY' => 'foo',
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
- }
- end
- let(:command_env) { { 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'foo:bar' } }
-
- before do
- allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash)
- end
def args_for_popen(args_list)
[Gitlab.config.git.bin_path, 'rev-list', *args_list]
@@ -23,7 +12,7 @@ describe Gitlab::Git::RevList do
params = [
args_for_popen(additional_args),
repository.path,
- command_env,
+ {},
hash_including(lazy_block: with_lazy_block ? anything : nil)
]
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index 872377c93d8..f03c7e3f04b 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -58,4 +58,14 @@ describe Gitlab::GitalyClient::RemoteService do
client.update_remote_mirror(ref_name, only_branches_matching)
end
end
+
+ describe '.exists?' do
+ context "when the remote doesn't exist" do
+ let(:url) { 'https://gitlab.com/gitlab-org/ik-besta-niet-of-ik-word-geplaagd.git' }
+
+ it 'returns false' do
+ expect(described_class.exists?(url)).to be(false)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/util_spec.rb b/spec/lib/gitlab/gitaly_client/util_spec.rb
index d1e0136f8c1..550db6db6d9 100644
--- a/spec/lib/gitlab/gitaly_client/util_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/util_spec.rb
@@ -7,16 +7,19 @@ describe Gitlab::GitalyClient::Util do
let(:gl_repository) { 'project-1' }
let(:git_object_directory) { '.git/objects' }
let(:git_alternate_object_directory) { ['/dir/one', '/dir/two'] }
+ let(:git_env) do
+ {
+ 'GIT_OBJECT_DIRECTORY_RELATIVE' => git_object_directory,
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => git_alternate_object_directory
+ }
+ end
subject do
described_class.repository(repository_storage, relative_path, gl_repository)
end
it 'creates a Gitaly::Repository with the given data' do
- allow(Gitlab::Git::Env).to receive(:[]).with('GIT_OBJECT_DIRECTORY_RELATIVE')
- .and_return(git_object_directory)
- allow(Gitlab::Git::Env).to receive(:[]).with('GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE')
- .and_return(git_alternate_object_directory)
+ allow(Gitlab::Git::HookEnv).to receive(:all).with(gl_repository).and_return(git_env)
expect(subject).to be_a(Gitaly::Repository)
expect(subject.storage_name).to eq(repository_storage)
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index 5bedfc79dd3..1f0f1fdd7da 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -38,8 +38,12 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
expect(project)
.to receive(:wiki_repository_exists?)
.and_return(false)
+ expect(Gitlab::GitalyClient::RemoteService)
+ .to receive(:exists?)
+ .with("foo.wiki.git")
+ .and_return(true)
- expect(importer.import_wiki?).to eq(true)
+ expect(importer.import_wiki?).to be(true)
end
it 'returns false if the GitHub wiki is disabled' do
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index b0bc081a3c8..d0dadfa78da 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -12,11 +12,11 @@ describe Gitlab::HTTP do
end
it 'deny requests to localhost' do
- expect { described_class.get('http://localhost:3003') }.to raise_error(URI::InvalidURIError)
+ expect { described_class.get('http://localhost:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
end
it 'deny requests to private network' do
- expect { described_class.get('http://192.168.1.2:3003') }.to raise_error(URI::InvalidURIError)
+ expect { described_class.get('http://192.168.1.2:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
end
context 'if allow_local_requests set to true' do
@@ -41,7 +41,7 @@ describe Gitlab::HTTP do
context 'if allow_local_requests set to false' do
it 'override the global value and ban requests to localhost or private network' do
- expect { described_class.get('http://localhost:3003', allow_local_requests: false) }.to raise_error(URI::InvalidURIError)
+ expect { described_class.get('http://localhost:3003', allow_local_requests: false) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
end
end
end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
new file mode 100644
index 00000000000..ed54d87de4a
--- /dev/null
+++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
+ let!(:service) { described_class.new }
+ let!(:project) { create(:project, :with_export) }
+ let(:shared) { project.import_export_shared }
+ let!(:user) { create(:user) }
+
+ describe '#execute' do
+ before do
+ allow(service).to receive(:strategy_execute)
+ end
+
+ it 'returns if project exported file is not found' do
+ allow(project).to receive(:export_project_path).and_return(nil)
+
+ expect(service).not_to receive(:strategy_execute)
+
+ service.execute(user, project)
+ end
+
+ it 'creates a lock file in the export dir' do
+ allow(service).to receive(:delete_after_export_lock)
+
+ service.execute(user, project)
+
+ expect(lock_path_exist?).to be_truthy
+ end
+
+ context 'when the method succeeds' do
+ it 'removes the lock file' do
+ service.execute(user, project)
+
+ expect(lock_path_exist?).to be_falsey
+ end
+ end
+
+ context 'when the method fails' do
+ before do
+ allow(service).to receive(:strategy_execute).and_call_original
+ end
+
+ context 'when validation fails' do
+ before do
+ allow(service).to receive(:invalid?).and_return(true)
+ end
+
+ it 'does not create the lock file' do
+ expect(service).not_to receive(:create_or_update_after_export_lock)
+
+ service.execute(user, project)
+ end
+
+ it 'does not execute main logic' do
+ expect(service).not_to receive(:strategy_execute)
+
+ service.execute(user, project)
+ end
+
+ it 'logs validation errors in shared context' do
+ expect(service).to receive(:log_validation_errors)
+
+ service.execute(user, project)
+ end
+ end
+
+ context 'when an exception is raised' do
+ it 'removes the lock' do
+ expect { service.execute(user, project) }.to raise_error(NotImplementedError)
+
+ expect(lock_path_exist?).to be_falsey
+ end
+ end
+ end
+ end
+
+ describe '#log_validation_errors' do
+ it 'add the message to the shared context' do
+ errors = %w(test_message test_message2)
+
+ allow(service).to receive(:invalid?).and_return(true)
+ allow(service.errors).to receive(:full_messages).and_return(errors)
+
+ expect(shared).to receive(:add_error_message).twice.and_call_original
+
+ service.execute(user, project)
+
+ expect(shared.errors).to eq errors
+ end
+ end
+
+ describe '#to_json' do
+ it 'adds the current strategy class to the serialized attributes' do
+ params = { param1: 1 }
+ result = params.merge(klass: described_class.to_s).to_json
+
+ expect(described_class.new(params).to_json).to eq result
+ end
+ end
+
+ def lock_path_exist?
+ File.exist?(described_class.lock_file_path(project))
+ end
+end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
new file mode 100644
index 00000000000..5fe57d9987b
--- /dev/null
+++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
+ let(:example_url) { 'http://www.example.com' }
+ let(:strategy) { subject.new(url: example_url, http_method: 'post') }
+ let!(:project) { create(:project, :with_export) }
+ let!(:user) { build(:user) }
+
+ subject { described_class }
+
+ describe 'validations' do
+ it 'only POST and PUT method allowed' do
+ %w(POST post PUT put).each do |method|
+ expect(subject.new(url: example_url, http_method: method)).to be_valid
+ end
+
+ expect(subject.new(url: example_url, http_method: 'whatever')).not_to be_valid
+ end
+
+ it 'onyl allow urls as upload urls' do
+ expect(subject.new(url: example_url)).to be_valid
+ expect(subject.new(url: 'whatever')).not_to be_valid
+ end
+ end
+
+ describe '#execute' do
+ it 'removes the exported project file after the upload' do
+ allow(strategy).to receive(:send_file)
+ allow(strategy).to receive(:handle_response_error)
+
+ expect(project).to receive(:remove_exported_project_file)
+
+ strategy.execute(user, project)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
new file mode 100644
index 00000000000..bf727285a9f
--- /dev/null
+++ b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AfterExportStrategyBuilder do
+ let!(:strategies_namespace) { 'Gitlab::ImportExport::AfterExportStrategies' }
+
+ describe '.build!' do
+ context 'when klass param is' do
+ it 'null it returns the default strategy' do
+ expect(described_class.build!(nil).class).to eq described_class.default_strategy
+ end
+
+ it 'not a valid class it raises StrategyNotFoundError exception' do
+ expect { described_class.build!('Whatever') }.to raise_error(described_class::StrategyNotFoundError)
+ end
+
+ it 'not a descendant of AfterExportStrategy' do
+ expect { described_class.build!('User') }.to raise_error(described_class::StrategyNotFoundError)
+ end
+ end
+
+ it 'initializes strategy with attributes param' do
+ params = { param1: 1, param2: 2, param3: 3 }
+
+ strategy = described_class.build!("#{strategies_namespace}::DownloadNotificationStrategy", params)
+
+ params.each { |k, v| expect(strategy.public_send(k)).to eq v }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index a204a8f1ffe..b675d5dc031 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -18,6 +18,7 @@ issues:
- metrics
- timelogs
- issue_assignees
+- closed_by
events:
- author
- project
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 0716852f57f..f949a23ffbb 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -15,6 +15,7 @@ Issue:
- updated_by_id
- confidential
- closed_at
+- closed_by_id
- due_date
- moved_to_id
- lock_version
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 2d35b026485..a3b3dc3be6d 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -74,13 +74,13 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false
end
- context 'when allow_private_networks is' do
- let(:private_networks) { ['192.168.1.2', '10.0.0.2', '172.16.0.2'] }
+ context 'when allow_local_network is' do
+ let(:local_ips) { ['192.168.1.2', '10.0.0.2', '172.16.0.2'] }
let(:fake_domain) { 'www.fakedomain.fake' }
context 'true (default)' do
it 'does not block urls from private networks' do
- private_networks.each do |ip|
+ local_ips.each do |ip|
stub_domain_resolv(fake_domain, ip)
expect(described_class).not_to be_blocked_url("http://#{fake_domain}")
@@ -94,14 +94,14 @@ describe Gitlab::UrlBlocker do
context 'false' do
it 'blocks urls from private networks' do
- private_networks.each do |ip|
+ local_ips.each do |ip|
stub_domain_resolv(fake_domain, ip)
- expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_private_networks: false)
+ expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_local_network: false)
unstub_domain_resolv
- expect(described_class).to be_blocked_url("http://#{ip}", allow_private_networks: false)
+ expect(described_class).to be_blocked_url("http://#{ip}", allow_local_network: false)
end
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 138d21ede97..9e6aa109a4b 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -12,6 +12,14 @@ describe Gitlab::UsageData do
create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
create(:service, project: projects[1], type: 'SlackService', active: true)
create(:service, project: projects[2], type: 'SlackService', active: true)
+
+ gcp_cluster = create(:cluster, :provided_by_gcp)
+ create(:cluster, :provided_by_user)
+ create(:cluster, :provided_by_user, :disabled)
+ create(:clusters_applications_helm, :installed, cluster: gcp_cluster)
+ create(:clusters_applications_ingress, :installed, cluster: gcp_cluster)
+ create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster)
+ create(:clusters_applications_runner, :installed, cluster: gcp_cluster)
end
subject { described_class.data }
@@ -64,6 +72,12 @@ describe Gitlab::UsageData do
clusters
clusters_enabled
clusters_disabled
+ clusters_platforms_gke
+ clusters_platforms_user
+ clusters_applications_helm
+ clusters_applications_ingress
+ clusters_applications_prometheus
+ clusters_applications_runner
in_review_folder
groups
issues
@@ -97,6 +111,15 @@ describe Gitlab::UsageData do
expect(count_data[:projects_jira_active]).to eq(2)
expect(count_data[:projects_slack_notifications_active]).to eq(2)
expect(count_data[:projects_slack_slash_active]).to eq(1)
+
+ expect(count_data[:clusters_enabled]).to eq(6)
+ expect(count_data[:clusters_disabled]).to eq(1)
+ expect(count_data[:clusters_platforms_gke]).to eq(1)
+ expect(count_data[:clusters_platforms_user]).to eq(1)
+ expect(count_data[:clusters_applications_helm]).to eq(1)
+ expect(count_data[:clusters_applications_ingress]).to eq(1)
+ expect(count_data[:clusters_applications_prometheus]).to eq(1)
+ expect(count_data[:clusters_applications_runner]).to eq(1)
end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 1d0faf56f7c..2b3ffb2d7c0 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -55,7 +55,7 @@ describe Gitlab::Workhorse do
end
end
- context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do
+ context 'when Gitaly workhorse_archive feature is disabled', :disable_gitaly do
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
@@ -100,7 +100,7 @@ describe Gitlab::Workhorse do
end
end
- context 'when Gitaly workhorse_send_git_patch feature is disabled', :skip_gitaly_mock do
+ context 'when Gitaly workhorse_send_git_patch feature is disabled', :disable_gitaly do
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
@@ -173,7 +173,7 @@ describe Gitlab::Workhorse do
end
end
- context 'when Gitaly workhorse_send_git_diff feature is disabled', :skip_gitaly_mock do
+ context 'when Gitaly workhorse_send_git_diff feature is disabled', :disable_gitaly do
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
@@ -275,12 +275,14 @@ describe Gitlab::Workhorse do
describe '.git_http_ok' do
let(:user) { create(:user) }
+ let(:repo_path) { 'ignored but not allowed to be empty in gitlab-workhorse' }
let(:action) { 'info_refs' }
let(:params) do
{
GL_ID: "user-#{user.id}",
GL_USERNAME: user.username,
GL_REPOSITORY: "project-#{project.id}",
+ RepoPath: repo_path,
ShowAllRefs: false
}
end
@@ -295,6 +297,7 @@ describe Gitlab::Workhorse do
GL_ID: "user-#{user.id}",
GL_USERNAME: user.username,
GL_REPOSITORY: "wiki-#{project.id}",
+ RepoPath: repo_path,
ShowAllRefs: false
}
end
@@ -452,7 +455,7 @@ describe Gitlab::Workhorse do
end
end
- context 'when Gitaly workhorse_raw_show feature is disabled', :skip_gitaly_mock do
+ context 'when Gitaly workhorse_raw_show feature is disabled', :disable_gitaly do
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
diff --git a/spec/mailers/previews/notify_preview.rb b/spec/mailers/previews/notify_preview.rb
index 580f0d56a92..43c3c89f140 100644
--- a/spec/mailers/previews/notify_preview.rb
+++ b/spec/mailers/previews/notify_preview.rb
@@ -65,7 +65,7 @@ class NotifyPreview < ActionMailer::Preview
end
def merge_request
- @merge_request ||= project.merge_requests.find_by(source_branch: 'master', target_branch: 'feature')
+ @merge_request ||= project.merge_requests.first
end
def user
diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb
index 4e72d9d748e..0014bbcf9f5 100644
--- a/spec/models/ci/artifact_blob_spec.rb
+++ b/spec/models/ci/artifact_blob_spec.rb
@@ -65,6 +65,19 @@ describe Ci::ArtifactBlob do
expect(url).not_to be_nil
expect(url).to eq("http://#{project.namespace.path}.#{Gitlab.config.pages.host}/-/#{project.path}/-/jobs/#{build.id}/artifacts/#{path}")
end
+
+ context 'when port is configured' do
+ let(:port) { 1234 }
+
+ it 'returns an URL with port number' do
+ allow(Gitlab.config.pages).to receive(:url).and_return("#{Gitlab.config.pages.url}:#{port}")
+
+ url = subject.external_url(build.project, build)
+
+ expect(url).not_to be_nil
+ expect(url).to eq("http://#{project.namespace.path}.#{Gitlab.config.pages.host}:#{port}/-/#{project.path}/-/jobs/#{build.id}/artifacts/#{path}")
+ end
+ end
end
end
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
new file mode 100644
index 00000000000..268561ee941
--- /dev/null
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe Ci::BuildMetadata do
+ set(:user) { create(:user) }
+ set(:group) { create(:group, :access_requestable) }
+ set(:project) { create(:project, :repository, group: group, build_timeout: 2000) }
+
+ 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) }
+ let(:build_metadata) { create(:ci_build_metadata, build: build) }
+
+ describe '#update_timeout_state' do
+ subject { build_metadata }
+
+ context 'when runner is not assigned to the job' do
+ it "doesn't change timeout value" do
+ expect { subject.update_timeout_state }.not_to change { subject.reload.timeout }
+ end
+
+ it "doesn't change timeout_source value" do
+ expect { subject.update_timeout_state }.not_to change { subject.reload.timeout_source }
+ end
+ end
+
+ context 'when runner is assigned to the job' do
+ before do
+ build.update_attributes(runner: runner)
+ end
+
+ context 'when runner timeout is lower than project timeout' do
+ let(:runner) { create(:ci_runner, maximum_timeout: 1900) }
+
+ it 'sets runner timeout' do
+ expect { subject.update_timeout_state }.to change { subject.reload.timeout }.to(1900)
+ end
+
+ it 'sets runner_timeout_source' do
+ expect { subject.update_timeout_state }.to change { subject.reload.timeout_source }.to('runner_timeout_source')
+ end
+ end
+
+ context 'when runner timeout is higher than project timeout' do
+ let(:runner) { create(:ci_runner, maximum_timeout: 2100) }
+
+ it 'sets project timeout' do
+ expect { subject.update_timeout_state }.to change { subject.reload.timeout }.to(2000)
+ end
+
+ it 'sets project_timeout_source' do
+ expect { subject.update_timeout_state }.to change { subject.reload.timeout_source }.to('project_timeout_source')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 7d935cf8d76..a12717835b0 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1271,12 +1271,6 @@ describe Ci::Build do
end
describe 'project settings' do
- describe '#timeout' do
- it 'returns project timeout configuration' do
- expect(build.timeout).to eq(project.build_timeout)
- end
- end
-
describe '#allow_git_fetch' do
it 'return project allow_git_fetch configuration' do
expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch)
@@ -1469,24 +1463,24 @@ describe Ci::Build do
let(:container_registry_enabled) { false }
let(:predefined_variables) do
[
+ { key: 'CI_JOB_ID', value: build.id.to_s, public: true },
+ { key: 'CI_JOB_TOKEN', value: build.token, public: false },
+ { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
+ { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
+ { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
+ { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
+ { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
{ key: 'GITLAB_FEATURES', value: project.namespace.features.join(','), public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
- { key: 'CI_JOB_ID', value: build.id.to_s, public: true },
{ key: 'CI_JOB_NAME', value: 'test', public: true },
{ key: 'CI_JOB_STAGE', value: 'test', public: true },
- { key: 'CI_JOB_TOKEN', value: build.token, public: false },
{ key: 'CI_COMMIT_SHA', value: build.sha, public: true },
{ key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
{ key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
- { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
- { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
- { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
- { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
- { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
{ key: 'CI_BUILD_REF', value: build.sha, public: true },
{ key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
{ key: 'CI_BUILD_REF_NAME', value: build.ref, public: true },
@@ -1951,6 +1945,7 @@ describe Ci::Build do
before do
allow(build).to receive(:predefined_variables) { [build_pre_var] }
allow(build).to receive(:yaml_variables) { [build_yaml_var] }
+ allow(build).to receive(:persisted_variables) { [] }
allow_any_instance_of(Project)
.to receive(:predefined_variables) { [project_pre_var] }
@@ -1999,6 +1994,106 @@ describe Ci::Build do
end
end
end
+
+ context 'when build has not been persisted yet' do
+ let(:build) do
+ described_class.new(
+ name: 'rspec',
+ stage: 'test',
+ ref: 'feature',
+ project: project,
+ pipeline: pipeline
+ )
+ end
+
+ it 'returns static predefined variables' do
+ expect(build.variables.size).to be >= 28
+ expect(build.variables)
+ .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true)
+ expect(build).not_to be_persisted
+ end
+ end
+ end
+
+ describe '#scoped_variables' do
+ context 'when build has not been persisted yet' do
+ let(:build) do
+ described_class.new(
+ name: 'rspec',
+ stage: 'test',
+ ref: 'feature',
+ project: project,
+ pipeline: pipeline
+ )
+ end
+
+ it 'does not persist the build' do
+ expect(build).to be_valid
+ expect(build).not_to be_persisted
+
+ build.scoped_variables
+
+ expect(build).not_to be_persisted
+ end
+
+ it 'returns static predefined variables' do
+ keys = %w[CI_JOB_NAME
+ CI_COMMIT_SHA
+ CI_COMMIT_REF_NAME
+ CI_COMMIT_REF_SLUG
+ CI_JOB_STAGE]
+
+ variables = build.scoped_variables
+
+ variables.map { |env| env[:key] }.tap do |names|
+ expect(names).to include(*keys)
+ end
+
+ expect(variables)
+ .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true)
+ end
+
+ it 'does not return prohibited variables' do
+ keys = %w[CI_JOB_ID
+ CI_JOB_TOKEN
+ CI_BUILD_ID
+ CI_BUILD_TOKEN
+ CI_REGISTRY_USER
+ CI_REGISTRY_PASSWORD
+ CI_REPOSITORY_URL
+ CI_ENVIRONMENT_URL]
+
+ build.scoped_variables.map { |env| env[:key] }.tap do |names|
+ expect(names).not_to include(*keys)
+ end
+ end
+ end
+ end
+
+ describe '#scoped_variables_hash' do
+ context 'when overriding secret variables' do
+ before do
+ project.variables.create!(key: 'MY_VAR', value: 'my value 1')
+ pipeline.variables.create!(key: 'MY_VAR', value: 'my value 2')
+ end
+
+ it 'returns a regular hash created using valid ordering' do
+ expect(build.scoped_variables_hash).to include('MY_VAR': 'my value 2')
+ expect(build.scoped_variables_hash).not_to include('MY_VAR': 'my value 1')
+ end
+ end
+
+ context 'when overriding user-provided variables' do
+ before do
+ pipeline.variables.build(key: 'MY_VAR', value: 'pipeline value')
+ build.yaml_variables = [{ key: 'MY_VAR', value: 'myvar', public: true }]
+ end
+
+ it 'returns a hash including variable with higher precedence' do
+ expect(build.scoped_variables_hash).to include('MY_VAR': 'pipeline value')
+ expect(build.scoped_variables_hash).not_to include('MY_VAR': 'myvar')
+ end
+ end
end
describe 'state transition: any => [:pending]' do
@@ -2011,6 +2106,70 @@ describe Ci::Build do
end
end
+ describe 'state transition: pending: :running' do
+ let(:runner) { create(:ci_runner) }
+ let(:job) { create(:ci_build, :pending, runner: runner) }
+
+ before do
+ job.project.update_attribute(:build_timeout, 1800)
+ end
+
+ def run_job_without_exception
+ job.run!
+ rescue StateMachines::InvalidTransition
+ end
+
+ shared_examples 'saves data on transition' do
+ it 'saves timeout' do
+ expect { job.run! }.to change { job.reload.ensure_metadata.timeout }.from(nil).to(expected_timeout)
+ end
+
+ it 'saves timeout_source' do
+ expect { job.run! }.to change { job.reload.ensure_metadata.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source)
+ end
+
+ context 'when Ci::BuildMetadata#update_timeout_state fails update' do
+ before do
+ allow_any_instance_of(Ci::BuildMetadata).to receive(:update_timeout_state).and_return(false)
+ end
+
+ it "doesn't save timeout" do
+ expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout_source }
+ end
+
+ it "doesn't save timeout_source" do
+ expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout_source }
+ end
+
+ it 'raises an exception' do
+ expect { job.run! }.to raise_error(StateMachines::InvalidTransition)
+ end
+ end
+ end
+
+ context 'when runner timeout overrides project timeout' do
+ let(:expected_timeout) { 900 }
+ let(:expected_timeout_source) { 'runner_timeout_source' }
+
+ before do
+ runner.update_attribute(:maximum_timeout, 900)
+ end
+
+ it_behaves_like 'saves data on transition'
+ end
+
+ context "when runner timeout doesn't override project timeout" do
+ let(:expected_timeout) { 1800 }
+ let(:expected_timeout_source) { 'project_timeout_source' }
+
+ before do
+ runner.update_attribute(:maximum_timeout, 3600)
+ end
+
+ it_behaves_like 'saves data on transition'
+ end
+ end
+
describe 'state transition: any => [:running]' do
shared_examples 'validation is active' do
context 'when depended job has not been completed yet' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 92f00cfbc19..dd94515b0a4 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -346,6 +346,20 @@ describe Ci::Pipeline, :mailer do
end
end
end
+
+ context 'when variables policy is specified' do
+ let(:config) do
+ { unit: { script: 'minitest', only: { variables: ['$CI_PIPELINE_SOURCE'] } },
+ feature: { script: 'spinach', only: { variables: ['$UNDEFINED'] } } }
+ end
+
+ it 'returns stage seeds only when variables expression is truthy' do
+ seeds = pipeline.stage_seeds
+
+ expect(seeds.size).to eq 1
+ expect(seeds.dig(0, 0, :name)).to eq 'unit'
+ end
+ end
end
describe '#seeds_size' do
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index ba7bad617b4..0eb1e3876e2 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -3,6 +3,18 @@ require 'rails_helper'
describe Clusters::Applications::Helm do
include_examples 'cluster application core specs', :clusters_applications_helm
+ describe '.installed' do
+ subject { described_class.installed }
+
+ let!(:cluster) { create(:clusters_applications_helm, :installed) }
+
+ before do
+ create(:clusters_applications_helm, :errored)
+ end
+
+ it { is_expected.to contain_exactly(cluster) }
+ end
+
describe '#install_command' do
let(:helm) { create(:clusters_applications_helm) }
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 03f5b88a525..a47a07d908d 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -11,6 +11,18 @@ describe Clusters::Applications::Ingress do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
end
+ describe '.installed' do
+ subject { described_class.installed }
+
+ let!(:cluster) { create(:clusters_applications_ingress, :installed) }
+
+ before do
+ create(:clusters_applications_ingress, :errored)
+ end
+
+ it { is_expected.to contain_exactly(cluster) }
+ end
+
describe '#make_installed!' do
before do
application.make_installed!
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 2905b58066b..aeca6ee903a 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -4,6 +4,18 @@ describe Clusters::Applications::Prometheus do
include_examples 'cluster application core specs', :clusters_applications_prometheus
include_examples 'cluster application status specs', :cluster_application_prometheus
+ describe '.installed' do
+ subject { described_class.installed }
+
+ let!(:cluster) { create(:clusters_applications_prometheus, :installed) }
+
+ before do
+ create(:clusters_applications_prometheus, :errored)
+ end
+
+ it { is_expected.to contain_exactly(cluster) }
+ end
+
describe 'transition to installed' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, projects: [project]) }
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index a574779e39d..64d995a73c1 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -8,6 +8,18 @@ describe Clusters::Applications::Runner do
it { is_expected.to belong_to(:runner) }
+ describe '.installed' do
+ subject { described_class.installed }
+
+ let!(:cluster) { create(:clusters_applications_runner, :installed) }
+
+ before do
+ create(:clusters_applications_runner, :errored)
+ end
+
+ it { is_expected.to contain_exactly(cluster) }
+ end
+
describe '#install_command' do
let(:kubeclient) { double('kubernetes client') }
let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) }
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 8f12a0e3085..b942554d67b 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -39,6 +39,42 @@ describe Clusters::Cluster do
it { is_expected.to contain_exactly(cluster) }
end
+ describe '.user_provided' do
+ subject { described_class.user_provided }
+
+ let!(:cluster) { create(:cluster, :provided_by_user) }
+
+ before do
+ create(:cluster, :provided_by_gcp)
+ end
+
+ it { is_expected.to contain_exactly(cluster) }
+ end
+
+ describe '.gcp_provided' do
+ subject { described_class.gcp_provided }
+
+ let!(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ before do
+ create(:cluster, :provided_by_user)
+ end
+
+ it { is_expected.to contain_exactly(cluster) }
+ end
+
+ describe '.gcp_installed' do
+ subject { described_class.gcp_installed }
+
+ let!(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ before do
+ create(:cluster, :providing_by_gcp)
+ end
+
+ it { is_expected.to contain_exactly(cluster) }
+ end
+
describe 'validation' do
subject { cluster.valid? }
diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb
new file mode 100644
index 00000000000..27c86e60e60
--- /dev/null
+++ b/spec/models/concerns/chronic_duration_attribute_spec.rb
@@ -0,0 +1,115 @@
+require 'spec_helper'
+
+shared_examples 'ChronicDurationAttribute reader' do
+ it 'contains dynamically created reader method' do
+ expect(subject.class).to be_public_method_defined(virtual_field)
+ end
+
+ it 'outputs chronic duration formatted value' do
+ subject.send("#{source_field}=", 120)
+
+ expect(subject.send(virtual_field)).to eq('2m')
+ end
+
+ context 'when value is set to nil' do
+ it 'outputs nil' do
+ subject.send("#{source_field}=", nil)
+
+ expect(subject.send(virtual_field)).to be_nil
+ end
+ end
+end
+
+shared_examples 'ChronicDurationAttribute writer' do
+ it 'contains dynamically created writer method' do
+ expect(subject.class).to be_public_method_defined("#{virtual_field}=")
+ end
+
+ before do
+ subject.send("#{virtual_field}=", '10m')
+ end
+
+ it 'parses chronic duration input' do
+ expect(subject.send(source_field)).to eq(600)
+ end
+
+ it 'passes validation' do
+ expect(subject.valid?).to be_truthy
+ end
+
+ context 'when negative input is used' do
+ before do
+ subject.send("#{source_field}=", 3600)
+ end
+
+ it "doesn't raise exception" do
+ expect { subject.send("#{virtual_field}=", '-10m') }.not_to raise_error(ChronicDuration::DurationParseError)
+ end
+
+ it "doesn't change value" do
+ expect { subject.send("#{virtual_field}=", '-10m') }.not_to change { subject.send(source_field) }
+ end
+
+ it "doesn't pass validation" do
+ subject.send("#{virtual_field}=", '-10m')
+
+ expect(subject.valid?).to be_falsey
+ expect(subject.errors&.messages).to include(virtual_field => ['is not a correct duration'])
+ end
+ end
+
+ context 'when empty input is used' do
+ before do
+ subject.send("#{virtual_field}=", '')
+ end
+
+ it 'writes nil' do
+ expect(subject.send(source_field)).to be_nil
+ end
+
+ it 'passes validation' do
+ expect(subject.valid?).to be_truthy
+ end
+ end
+
+ context 'when nil input is used' do
+ before do
+ subject.send("#{virtual_field}=", nil)
+ end
+
+ it 'writes nil' do
+ expect(subject.send(source_field)).to be_nil
+ end
+
+ it 'passes validation' do
+ expect(subject.valid?).to be_truthy
+ end
+
+ it "doesn't raise exception" do
+ expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error(NoMethodError)
+ end
+ end
+end
+
+describe 'ChronicDurationAttribute' do
+ let(:source_field) {:maximum_timeout}
+ let(:virtual_field) {:maximum_timeout_human_readable}
+
+ subject { Ci::Runner.new }
+
+ it_behaves_like 'ChronicDurationAttribute reader'
+ it_behaves_like 'ChronicDurationAttribute writer'
+end
+
+describe 'ChronicDurationAttribute - reader' do
+ let(:source_field) {:timeout}
+ let(:virtual_field) {:timeout_human_readable}
+
+ subject {Ci::BuildMetadata.new}
+
+ it "doesn't contain dynamically created writer method" do
+ expect(subject.class).not_to be_public_method_defined("#{virtual_field}=")
+ end
+
+ it_behaves_like 'ChronicDurationAttribute reader'
+end
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 3d7283e2164..41440c6d288 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -17,4 +17,25 @@ describe DeployKey, :mailer do
should_not_email(user)
end
end
+
+ describe '#user' do
+ let(:deploy_key) { create(:deploy_key) }
+ let(:user) { create(:user) }
+
+ context 'when user is set' do
+ before do
+ deploy_key.user = user
+ end
+
+ it 'returns the user' do
+ expect(deploy_key.user).to be(user)
+ end
+ end
+
+ context 'when user is not set' do
+ it 'returns the ghost user' do
+ expect(deploy_key.user).to eq(User.ghost)
+ end
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 1a7a6e035ea..fef868ac0f2 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -224,14 +224,14 @@ describe Project do
project2 = build(:project, import_url: 'http://localhost:9000/t.git')
expect(project2).to be_invalid
- expect(project2.errors[:import_url]).to include('imports are not allowed from that URL')
+ expect(project2.errors[:import_url].first).to include('Requests to localhost are not allowed')
end
it "does not allow blocked import_url port" do
project2 = build(:project, import_url: 'http://github.com:25/t.git')
expect(project2).to be_invalid
- expect(project2.errors[:import_url]).to include('imports are not allowed from that URL')
+ expect(project2.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
end
describe 'project pending deletion' do
@@ -1265,6 +1265,34 @@ describe Project do
end
end
+ describe '#pages_group_url' do
+ let(:group) { create :group, name: group_name }
+ let(:project) { create :project, namespace: group, name: project_name }
+ let(:domain) { 'Example.com' }
+ let(:port) { 1234 }
+
+ subject { project.pages_group_url }
+
+ before do
+ allow(Settings.pages).to receive(:host).and_return(domain)
+ allow(Gitlab.config.pages).to receive(:url).and_return("http://example.com:#{port}")
+ end
+
+ context 'group page' do
+ let(:group_name) { 'Group' }
+ let(:project_name) { 'group.example.com' }
+
+ it { is_expected.to eq("http://group.example.com:#{port}") }
+ end
+
+ context 'project page' do
+ let(:group_name) { 'Group' }
+ let(:project_name) { 'Project' }
+
+ it { is_expected.to eq("http://group.example.com:#{port}") }
+ end
+ end
+
describe '.search' do
let(:project) { create(:project, description: 'kitten mittens') }
@@ -2532,7 +2560,7 @@ describe Project do
end
end
- describe '#remove_exports' do
+ describe '#remove_export' do
let(:legacy_project) { create(:project, :legacy_storage, :with_export) }
let(:project) { create(:project, :with_export) }
@@ -2580,6 +2608,23 @@ describe Project do
end
end
+ describe '#remove_exported_project_file' do
+ let(:project) { create(:project, :with_export) }
+
+ it 'removes the exported project file' do
+ exported_file = project.export_project_path
+
+ expect(File.exist?(exported_file)).to be_truthy
+
+ allow(FileUtils).to receive(:rm_f).and_call_original
+ expect(FileUtils).to receive(:rm_f).with(exported_file).and_call_original
+
+ project.remove_exported_project_file
+
+ expect(File.exist?(exported_file)).to be_falsy
+ end
+ end
+
describe '#forks_count' do
it 'returns the number of forks' do
project = build(:project)
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 79f25dc4360..83ed3b203e6 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -58,6 +58,21 @@ describe Service do
end
describe "Template" do
+ describe '.build_from_template' do
+ context 'when template is invalid' do
+ it 'sets service template to inactive when template is invalid' do
+ project = create(:project)
+ template = JiraService.new(template: true, active: true)
+ template.save(validate: false)
+
+ service = described_class.build_from_template(project.id, template)
+
+ expect(service).to be_valid
+ expect(service.active).to be false
+ end
+ end
+ end
+
describe "for pushover service" do
let!(:service_template) do
PushoverService.create(
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index bbfdda23a31..100418da804 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -25,7 +25,7 @@ describe User do
it { is_expected.to have_many(:group_members) }
it { is_expected.to have_many(:groups) }
it { is_expected.to have_many(:keys).dependent(:destroy) }
- it { is_expected.to have_many(:deploy_keys).dependent(:destroy) }
+ it { is_expected.to have_many(:deploy_keys).dependent(:nullify) }
it { is_expected.to have_many(:events).dependent(:destroy) }
it { is_expected.to have_many(:issues).dependent(:destroy) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 852f67db958..8ad19e3f0f5 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -1141,4 +1141,33 @@ describe API::Commits do
end
end
end
+
+ describe 'GET /projects/:id/repository/commits/:sha/merge_requests' do
+ let!(:project) { create(:project, :repository, :private) }
+ let!(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') }
+ let(:commit) { merged_mr.merge_request_diff.commits.last }
+
+ it 'returns the correct merge request' do
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}/merge_requests", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response.length).to eq(1)
+ expect(json_response[0]['id']).to eq(merged_mr.id)
+ end
+
+ it 'returns 403 for an unauthorized user' do
+ project.add_guest(user)
+
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}/merge_requests", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'responds 404 when the commit does not exist' do
+ get api("/projects/#{project.id}/repository/commits/a7d26f00c35b/merge_requests", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 0772b3f2e64..ae9c0e9c304 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -91,6 +91,10 @@ describe API::DeployKeys do
expect do
post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
end.to change { project.deploy_keys.count }.by(1)
+
+ new_key = project.deploy_keys.last
+ expect(new_key.key).to eq(key_attrs[:key])
+ expect(new_key.user).to eq(admin)
end
it 'returns an existing ssh key when attempting to add a duplicate' do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 3cb90a1b8ef..db8c5f963d6 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -251,44 +251,23 @@ describe API::Internal do
end
context 'with env passed as a JSON' do
- context 'when relative path envs are not set' do
- it 'sets env in RequestStore' do
- expect(Gitlab::Git::Env).to receive(:set).with({
- 'GIT_OBJECT_DIRECTORY' => 'foo',
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
- })
-
- push(key, project.wiki, env: {
- GIT_OBJECT_DIRECTORY: 'foo',
- GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar'
- }.to_json)
+ let(:gl_repository) { project.gl_repository(is_wiki: true) }
- expect(response).to have_gitlab_http_status(200)
- end
- end
+ it 'sets env in RequestStore' do
+ obj_dir_relative = './objects'
+ alt_obj_dirs_relative = ['./alt-objects-1', './alt-objects-2']
- context 'when relative path envs are set' do
- it 'sets env in RequestStore' do
- obj_dir_relative = './objects'
- alt_obj_dirs_relative = ['./alt-objects-1', './alt-objects-2']
- repo_path = project.wiki.repository.path_to_repo
-
- expect(Gitlab::Git::Env).to receive(:set).with({
- 'GIT_OBJECT_DIRECTORY' => File.join(repo_path, obj_dir_relative),
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => alt_obj_dirs_relative.map { |d| File.join(repo_path, d) },
- 'GIT_OBJECT_DIRECTORY_RELATIVE' => obj_dir_relative,
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => alt_obj_dirs_relative
- })
-
- push(key, project.wiki, env: {
- GIT_OBJECT_DIRECTORY: 'foo',
- GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar',
- GIT_OBJECT_DIRECTORY_RELATIVE: obj_dir_relative,
- GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: alt_obj_dirs_relative
- }.to_json)
+ expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, {
+ 'GIT_OBJECT_DIRECTORY_RELATIVE' => obj_dir_relative,
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => alt_obj_dirs_relative
+ })
- expect(response).to have_gitlab_http_status(200)
- end
+ push(key, project.wiki, env: {
+ GIT_OBJECT_DIRECTORY_RELATIVE: obj_dir_relative,
+ GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: alt_obj_dirs_relative
+ }.to_json)
+
+ expect(response).to have_gitlab_http_status(200)
end
end
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 12583109b59..3834d27d0a9 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -5,6 +5,7 @@ describe API::ProjectExport do
set(:project_none) { create(:project) }
set(:project_started) { create(:project) }
set(:project_finished) { create(:project) }
+ set(:project_after_export) { create(:project) }
set(:user) { create(:user) }
set(:admin) { create(:admin) }
@@ -12,11 +13,13 @@ describe API::ProjectExport do
let(:path_none) { "/projects/#{project_none.id}/export" }
let(:path_started) { "/projects/#{project_started.id}/export" }
let(:path_finished) { "/projects/#{project_finished.id}/export" }
+ let(:path_after_export) { "/projects/#{project_after_export.id}/export" }
let(:download_path) { "/projects/#{project.id}/export/download" }
let(:download_path_none) { "/projects/#{project_none.id}/export/download" }
let(:download_path_started) { "/projects/#{project_started.id}/export/download" }
let(:download_path_finished) { "/projects/#{project_finished.id}/export/download" }
+ let(:download_path_export_action) { "/projects/#{project_after_export.id}/export/download" }
let(:export_path) { "#{Dir.tmpdir}/project_export_spec" }
@@ -29,6 +32,11 @@ describe API::ProjectExport do
# simulate exported
FileUtils.mkdir_p project_finished.export_path
FileUtils.touch File.join(project_finished.export_path, '_export.tar.gz')
+
+ # simulate in after export action
+ FileUtils.mkdir_p project_after_export.export_path
+ FileUtils.touch File.join(project_after_export.export_path, '_export.tar.gz')
+ FileUtils.touch Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy.lock_file_path(project_after_export)
end
after do
@@ -73,6 +81,14 @@ describe API::ProjectExport do
expect(json_response['export_status']).to eq('started')
end
+ it 'is after_export' do
+ get api(path_after_export, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/project/export_status')
+ expect(json_response['export_status']).to eq('after_export_action')
+ end
+
it 'is finished' do
get api(path_finished, user)
@@ -99,6 +115,7 @@ describe API::ProjectExport do
project_none.add_master(user)
project_started.add_master(user)
project_finished.add_master(user)
+ project_after_export.add_master(user)
end
it_behaves_like 'get project export status ok'
@@ -163,6 +180,36 @@ describe API::ProjectExport do
end
end
+ shared_examples_for 'get project export upload after action' do
+ context 'and is uploading' do
+ it 'downloads' do
+ get api(download_path_export_action, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'when upload complete' do
+ before do
+ FileUtils.rm_rf(project_after_export.export_path)
+ end
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(download_path_export_action, user) }
+ end
+ end
+ end
+
+ shared_examples_for 'get project download by strategy' do
+ context 'when upload strategy set' do
+ it_behaves_like 'get project export upload after action'
+ end
+
+ context 'when download strategy set' do
+ it_behaves_like 'get project export download'
+ end
+ end
+
it_behaves_like 'when project export is disabled' do
let(:request) { get api(download_path, admin) }
end
@@ -171,7 +218,7 @@ describe API::ProjectExport do
context 'when user is an admin' do
let(:user) { admin }
- it_behaves_like 'get project export download'
+ it_behaves_like 'get project download by strategy'
end
context 'when user is a master' do
@@ -180,9 +227,10 @@ describe API::ProjectExport do
project_none.add_master(user)
project_started.add_master(user)
project_finished.add_master(user)
+ project_after_export.add_master(user)
end
- it_behaves_like 'get project export download'
+ it_behaves_like 'get project download by strategy'
end
context 'when user is a developer' do
@@ -229,10 +277,30 @@ describe API::ProjectExport do
end
shared_examples_for 'post project export start' do
- it 'starts' do
- post api(path, user)
+ context 'with upload strategy' do
+ context 'when params invalid' do
+ it_behaves_like '400 response' do
+ let(:request) { post(api(path, user), 'upload[url]' => 'whatever') }
+ end
+ end
+
+ it 'starts' do
+ allow_any_instance_of(Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy).to receive(:send_file)
+
+ post(api(path, user), 'upload[url]' => 'http://gitlab.com')
- expect(response).to have_gitlab_http_status(202)
+ expect(response).to have_gitlab_http_status(202)
+ end
+ end
+
+ context 'with download strategy' do
+ it 'starts' do
+ expect_any_instance_of(Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy).not_to receive(:send_file)
+
+ post api(path, user)
+
+ expect(response).to have_gitlab_http_status(202)
+ end
end
end
@@ -253,6 +321,7 @@ describe API::ProjectExport do
project_none.add_master(user)
project_started.add_master(user)
project_finished.add_master(user)
+ project_after_export.add_master(user)
end
it_behaves_like 'post project export start'
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index f3dd121faa9..5084b36c761 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -109,6 +109,26 @@ describe API::Runner do
end
end
+ context 'when maximum job timeout is specified' do
+ it 'creates runner' do
+ post api('/runners'), token: registration_token,
+ maximum_timeout: 9000
+
+ expect(response).to have_gitlab_http_status 201
+ expect(Ci::Runner.first.maximum_timeout).to eq(9000)
+ end
+
+ context 'when maximum job timeout is empty' do
+ it 'creates runner' do
+ post api('/runners'), token: registration_token,
+ maximum_timeout: ''
+
+ expect(response).to have_gitlab_http_status 201
+ expect(Ci::Runner.first.maximum_timeout).to be_nil
+ end
+ end
+ end
+
%w(name version revision platform architecture).each do |param|
context "when info parameter '#{param}' info is present" do
let(:value) { "#{param}_value" }
@@ -340,12 +360,12 @@ describe API::Runner do
let(:expected_steps) do
[{ 'name' => 'script',
'script' => %w(ls date),
- 'timeout' => job.timeout,
+ 'timeout' => job.metadata_timeout,
'when' => 'on_success',
'allow_failure' => false },
{ 'name' => 'after_script',
'script' => %w(ls date),
- 'timeout' => job.timeout,
+ 'timeout' => job.metadata_timeout,
'when' => 'always',
'allow_failure' => true }]
end
@@ -648,6 +668,41 @@ describe API::Runner do
end
end
end
+
+ describe 'timeout support' do
+ context 'when project specifies job timeout' do
+ let(:project) { create(:project, shared_runners_enabled: false, build_timeout: 1234) }
+
+ it 'contains info about timeout taken from project' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
+ end
+
+ context 'when runner specifies lower timeout' do
+ let(:runner) { create(:ci_runner, maximum_timeout: 1000) }
+
+ it 'contains info about timeout overridden by runner' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['runner_info']).to include({ 'timeout' => 1000 })
+ end
+ end
+
+ context 'when runner specifies bigger timeout' do
+ let(:runner) { create(:ci_runner, maximum_timeout: 2000) }
+
+ it 'contains info about timeout not overridden by runner' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
+ end
+ end
+ end
+ end
end
def request_job(token = runner.token, **params)
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index ec5cad4f4fd..d30f0cf36e2 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -123,6 +123,7 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(200)
expect(json_response['description']).to eq(shared_runner.description)
+ expect(json_response['maximum_timeout']).to be_nil
end
end
@@ -192,7 +193,8 @@ describe API::Runners do
tag_list: ['ruby2.1', 'pgsql', 'mysql'],
run_untagged: 'false',
locked: 'true',
- access_level: 'ref_protected')
+ access_level: 'ref_protected',
+ maximum_timeout: 1234)
shared_runner.reload
expect(response).to have_gitlab_http_status(200)
@@ -204,6 +206,7 @@ describe API::Runners do
expect(shared_runner.ref_protected?).to be_truthy
expect(shared_runner.ensure_runner_queue_value)
.not_to eq(runner_queue_value)
+ expect(shared_runner.maximum_timeout).to eq(1234)
end
end
diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb
index 16431ed4188..70402bac2e2 100644
--- a/spec/serializers/status_entity_spec.rb
+++ b/spec/serializers/status_entity_spec.rb
@@ -25,5 +25,10 @@ describe StatusEntity do
allow(Rails.env).to receive(:development?) { true }
expect(entity.as_json[:favicon]).to match_asset_path('/assets/ci_favicons/dev/favicon_status_success.ico')
end
+
+ it 'contains a canary namespaced favicon if canary env' do
+ stub_env('CANARY', 'true')
+ expect(entity.as_json[:favicon]).to match_asset_path('/assets/ci_favicons/canary/favicon_status_success.ico')
+ end
end
end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index b86a3d72bb4..8de0bdf92e2 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -29,7 +29,8 @@ describe Ci::RetryBuildService do
commit_id deployments erased_by_id last_deployment project_id
runner_id tag_taggings taggings tags trigger_request_id
user_id auto_canceled_by_id retried failure_reason
- artifacts_file_store artifacts_metadata_store].freeze
+ artifacts_file_store artifacts_metadata_store
+ metadata].freeze
shared_examples 'build duplication' do
let(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 47c1ebbeb81..7ae49c06896 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -67,6 +67,10 @@ describe Issues::CloseService do
expect(issue).to be_closed
end
+ it 'records closed user' do
+ expect(issue.closed_by_id).to be(user.id)
+ end
+
it 'sends email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 4413c6ef83e..2cacb97a293 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -70,6 +70,16 @@ describe Projects::CreateService, '#execute' do
opts[:default_branch] = 'master'
expect(create_project(user, opts)).to eq(nil)
end
+
+ it 'sets invalid service as inactive' do
+ create(:service, type: 'JiraService', project: nil, template: true, active: true)
+
+ project = create_project(user, opts)
+ service = project.services.first
+
+ expect(project).to be_persisted
+ expect(service.active).to be false
+ end
end
context 'wiki_enabled creates repository directory' do
@@ -232,14 +242,15 @@ describe Projects::CreateService, '#execute' do
end
context 'when a bad service template is created' do
- it 'reports an error in the imported project' do
+ it 'sets service to be inactive' do
opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-ce'
create(:service, type: 'DroneCiService', project: nil, template: true, active: true)
project = create_project(user, opts)
+ service = project.services.first
- expect(project.errors.full_messages_for(:base).first).to match(/Unable to save project. Error: Unable to save DroneCiService/)
- expect(project.services.count).to eq 0
+ expect(project).to be_persisted
+ expect(service.active).to be false
end
end
diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb
new file mode 100644
index 00000000000..51491c7d529
--- /dev/null
+++ b/spec/services/projects/import_export/export_service_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe Projects::ImportExport::ExportService do
+ describe '#execute' do
+ let!(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:shared) { project.import_export_shared }
+ let(:service) { described_class.new(project, user) }
+ let!(:after_export_strategy) { Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy.new }
+
+ context 'when all saver services succeed' do
+ before do
+ allow(service).to receive(:save_services).and_return(true)
+ end
+
+ it 'saves the project in the file system' do
+ expect(Gitlab::ImportExport::Saver).to receive(:save).with(project: project, shared: shared)
+
+ service.execute
+ end
+
+ it 'calls the after export strategy' do
+ expect(after_export_strategy).to receive(:execute)
+
+ service.execute(after_export_strategy)
+ end
+
+ context 'when after export strategy fails' do
+ before do
+ allow(after_export_strategy).to receive(:execute).and_return(false)
+ end
+
+ after do
+ service.execute(after_export_strategy)
+ end
+
+ it 'removes the remaining exported data' do
+ allow(shared).to receive(:export_path).and_return('whatever')
+ allow(FileUtils).to receive(:rm_rf)
+
+ expect(FileUtils).to receive(:rm_rf).with(shared.export_path)
+ end
+
+ it 'notifies the user' do
+ expect_any_instance_of(NotificationService).to receive(:project_not_exported)
+ end
+
+ it 'notifies logger' do
+ allow(Rails.logger).to receive(:error)
+
+ expect(Rails.logger).to receive(:error)
+ end
+ end
+ end
+
+ context 'when saver services fail' do
+ before do
+ allow(service).to receive(:save_services).and_return(false)
+ end
+
+ after do
+ expect { service.execute }.to raise_error(Gitlab::ImportExport::Error)
+ end
+
+ it 'removes the remaining exported data' do
+ allow(shared).to receive(:export_path).and_return('whatever')
+ allow(FileUtils).to receive(:rm_rf)
+
+ expect(FileUtils).to receive(:rm_rf).with(shared.export_path)
+ end
+
+ it 'notifies the user' do
+ expect_any_instance_of(NotificationService).to receive(:project_not_exported)
+ end
+
+ it 'notifies logger' do
+ expect(Rails.logger).to receive(:error)
+ end
+
+ it 'the after export strategy is not called' do
+ expect(service).not_to receive(:execute_after_export_action)
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index bf7facaec99..30c89ebd821 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -156,7 +156,7 @@ describe Projects::ImportService do
result = described_class.new(project, user).execute
expect(result[:status]).to eq :error
- expect(result[:message]).to end_with 'Blocked import URL.'
+ expect(result[:message]).to include('Requests to localhost are not allowed')
end
it 'fails with port 25' do
@@ -165,7 +165,7 @@ describe Projects::ImportService do
result = described_class.new(project, user).execute
expect(result[:status]).to eq :error
- expect(result[:message]).to end_with 'Blocked import URL.'
+ expect(result[:message]).to include('Only allowed ports are 22, 80, 443')
end
end
diff --git a/spec/support/cookie_helper.rb b/spec/support/cookie_helper.rb
index d72925e1838..5ff7b0b68c9 100644
--- a/spec/support/cookie_helper.rb
+++ b/spec/support/cookie_helper.rb
@@ -2,12 +2,25 @@
#
module CookieHelper
def set_cookie(name, value, options = {})
+ case page.driver
+ when Capybara::RackTest::Driver
+ rack_set_cookie(name, value)
+ else
+ selenium_set_cookie(name, value, options)
+ end
+ end
+
+ def selenium_set_cookie(name, value, options = {})
# Selenium driver will not set cookies for a given domain when the browser is at `about:blank`.
# It also doesn't appear to allow overriding the cookie path. loading `/` is the most inclusive.
visit options.fetch(:path, '/') unless on_a_page?
page.driver.browser.manage.add_cookie(name: name, value: value, **options)
end
+ def rack_set_cookie(name, value)
+ page.driver.browser.set_cookie("#{name}=#{value}")
+ end
+
def get_cookie(name)
page.driver.browser.manage.cookie_named(name)
end
diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb
index c7e8a39a617..9cf541372b5 100644
--- a/spec/support/gitaly.rb
+++ b/spec/support/gitaly.rb
@@ -1,11 +1,13 @@
RSpec.configure do |config|
config.before(:each) do |example|
if example.metadata[:disable_gitaly]
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
+ # Use 'and_wrap_original' to make sure the arguments are valid
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_wrap_original { |m, *args| m.call(*args) && false }
else
next if example.metadata[:skip_gitaly_mock]
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
+ # Use 'and_wrap_original' to make sure the arguments are valid
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_wrap_original { |m, *args| m.call(*args) || true }
end
end
end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index d08183846a0..db34090e971 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -140,6 +140,10 @@ module LoginHelpers
end
allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
stub_omniauth_setting(messages)
+ stub_saml_authorize_path_helpers
+ end
+
+ def stub_saml_authorize_path_helpers
allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml')
allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
end
diff --git a/spec/support/migrations_helpers.rb b/spec/support/migrations_helpers.rb
index 6bf976a2cf9..5d6f662e8fe 100644
--- a/spec/support/migrations_helpers.rb
+++ b/spec/support/migrations_helpers.rb
@@ -1,6 +1,9 @@
module MigrationsHelpers
def table(name)
- Class.new(ActiveRecord::Base) { self.table_name = name }
+ Class.new(ActiveRecord::Base) do
+ self.table_name = name
+ self.inheritance_column = :_type_disabled
+ end
end
def migrations_paths
diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
index cd9974cd6e2..6352f1527cd 100644
--- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
@@ -61,12 +61,18 @@ shared_examples "migrates" do |to_store:, from_store: nil|
expect { migrate(to) }.not_to change { file.exists? }
end
- context 'when migrate! is not oqqupied by another process' do
+ context 'when migrate! is not occupied by another process' do
it 'executes migrate!' do
expect(subject).to receive(:object_store=).at_least(1)
migrate(to)
end
+
+ it 'executes use_file' do
+ expect(subject).to receive(:unsafe_use_file).once
+
+ subject.use_file
+ end
end
context 'when migrate! is occupied by another process' do
@@ -79,7 +85,13 @@ shared_examples "migrates" do |to_store:, from_store: nil|
it 'does not execute migrate!' do
expect(subject).not_to receive(:unsafe_migrate!)
- expect { migrate(to) }.to raise_error('Already running')
+ expect { migrate(to) }.to raise_error('exclusive lease already taken')
+ end
+
+ it 'does not execute use_file' do
+ expect(subject).not_to receive(:unsafe_use_file)
+
+ expect { subject.use_file }.to raise_error('exclusive lease already taken')
end
after do
diff --git a/spec/tasks/gitlab/uploads/migrate_rake_spec.rb b/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
index b778d26060d..6fcfae358ec 100644
--- a/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
@@ -1,10 +1,9 @@
require 'rake_helper'
describe 'gitlab:uploads:migrate rake tasks' do
- let!(:projects) { create_list(:project, 10, :with_avatar) }
- let(:model_class) { Project }
- let(:uploader_class) { AvatarUploader }
- let(:mounted_as) { :avatar }
+ let(:model_class) { nil }
+ let(:uploader_class) { nil }
+ let(:mounted_as) { nil }
let(:batch_size) { 3 }
before do
@@ -20,9 +19,125 @@ describe 'gitlab:uploads:migrate rake tasks' do
run_rake_task("gitlab:uploads:migrate", *args)
end
- it 'enqueue jobs in batch' do
- expect(ObjectStorage::MigrateUploadsWorker).to receive(:enqueue!).exactly(4).times
+ shared_examples 'enqueue jobs in batch' do |batch:|
+ it do
+ expect(ObjectStorage::MigrateUploadsWorker)
+ .to receive(:perform_async).exactly(batch).times
+ .and_return("A fake job.")
- run
+ run
+ end
+ end
+
+ context "for AvatarUploader" do
+ let(:uploader_class) { AvatarUploader }
+ let(:mounted_as) { :avatar }
+
+ context "for Project" do
+ let(:model_class) { Project }
+ let!(:projects) { create_list(:project, 10, :with_avatar) }
+
+ it_behaves_like 'enqueue jobs in batch', batch: 4
+
+ context 'Upload has store = nil' do
+ before do
+ Upload.where(model: projects).update_all(store: nil)
+ end
+
+ it_behaves_like 'enqueue jobs in batch', batch: 4
+ end
+ end
+
+ context "for Group" do
+ let(:model_class) { Group }
+
+ before do
+ create_list(:group, 10, :with_avatar)
+ end
+
+ it_behaves_like 'enqueue jobs in batch', batch: 4
+ end
+
+ context "for User" do
+ let(:model_class) { User }
+
+ before do
+ create_list(:user, 10, :with_avatar)
+ end
+
+ it_behaves_like 'enqueue jobs in batch', batch: 4
+ end
+ end
+
+ context "for AttachmentUploader" do
+ let(:uploader_class) { AttachmentUploader }
+
+ context "for Note" do
+ let(:model_class) { Note }
+ let(:mounted_as) { :attachment }
+
+ before do
+ create_list(:note, 10, :with_attachment)
+ end
+
+ it_behaves_like 'enqueue jobs in batch', batch: 4
+ end
+
+ context "for Appearance" do
+ let(:model_class) { Appearance }
+ let(:mounted_as) { :logo }
+
+ before do
+ create(:appearance, :with_logos)
+ end
+
+ %i(logo header_logo).each do |mount|
+ it_behaves_like 'enqueue jobs in batch', batch: 1 do
+ let(:mounted_as) { mount }
+ end
+ end
+ end
+ end
+
+ context "for FileUploader" do
+ let(:uploader_class) { FileUploader }
+ let(:model_class) { Project }
+
+ before do
+ create_list(:project, 10) do |model|
+ uploader_class.new(model)
+ .store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
+ end
+ end
+
+ it_behaves_like 'enqueue jobs in batch', batch: 4
+ end
+
+ context "for PersonalFileUploader" do
+ let(:uploader_class) { PersonalFileUploader }
+ let(:model_class) { PersonalSnippet }
+
+ before do
+ create_list(:personal_snippet, 10) do |model|
+ uploader_class.new(model)
+ .store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
+ end
+ end
+
+ it_behaves_like 'enqueue jobs in batch', batch: 4
+ end
+
+ context "for NamespaceFileUploader" do
+ let(:uploader_class) { NamespaceFileUploader }
+ let(:model_class) { Snippet }
+
+ before do
+ create_list(:snippet, 10) do |model|
+ uploader_class.new(model)
+ .store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
+ end
+ end
+
+ it_behaves_like 'enqueue jobs in batch', batch: 4
end
end
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index 1d406c71955..59e02fecbce 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -308,6 +308,30 @@ describe ObjectStorage do
it { is_expected.to eq(remote_directory) }
end
+ context 'when file is in use' do
+ def when_file_is_in_use
+ uploader.use_file do
+ yield
+ end
+ end
+
+ it 'cannot migrate' do
+ when_file_is_in_use do
+ expect(uploader).not_to receive(:unsafe_migrate!)
+
+ expect { uploader.migrate!(described_class::Store::REMOTE) }.to raise_error('exclusive lease already taken')
+ end
+ end
+
+ it 'cannot use_file' do
+ when_file_is_in_use do
+ expect(uploader).not_to receive(:unsafe_use_file)
+
+ expect { uploader.use_file }.to raise_error('exclusive lease already taken')
+ end
+ end
+ end
+
describe '#fog_credentials' do
let(:connection) { Settingslogic.new("provider" => "AWS") }
diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
new file mode 100644
index 00000000000..b34f427fd8a
--- /dev/null
+++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
@@ -0,0 +1,146 @@
+require 'spec_helper'
+
+describe ObjectStorage::BackgroundMoveWorker do
+ let(:local) { ObjectStorage::Store::LOCAL }
+ let(:remote) { ObjectStorage::Store::REMOTE }
+
+ def perform
+ described_class.perform_async(uploader_class.name, subject_class, file_field, subject_id)
+ end
+
+ context 'for LFS' do
+ let!(:lfs_object) { create(:lfs_object, :with_file, file_store: local) }
+ let(:uploader_class) { LfsObjectUploader }
+ let(:subject_class) { LfsObject }
+ let(:file_field) { :file }
+ let(:subject_id) { lfs_object.id }
+
+ context 'when object storage is enabled' do
+ before do
+ stub_lfs_object_storage(background_upload: true)
+ end
+
+ it 'uploads object to storage' do
+ expect { perform }.to change { lfs_object.reload.file_store }.from(local).to(remote)
+ end
+
+ context 'when background upload is disabled' do
+ before do
+ allow(Gitlab.config.lfs.object_store).to receive(:background_upload) { false }
+ end
+
+ it 'is skipped' do
+ expect { perform }.not_to change { lfs_object.reload.file_store }
+ end
+ end
+ end
+
+ context 'when object storage is disabled' do
+ before do
+ stub_lfs_object_storage(enabled: false)
+ end
+
+ it "doesn't migrate files" do
+ perform
+
+ expect(lfs_object.reload.file_store).to eq(local)
+ end
+ end
+ end
+
+ context 'for legacy artifacts' do
+ let(:build) { create(:ci_build, :legacy_artifacts) }
+ let(:uploader_class) { LegacyArtifactUploader }
+ let(:subject_class) { Ci::Build }
+ let(:file_field) { :artifacts_file }
+ let(:subject_id) { build.id }
+
+ context 'when local storage is used' do
+ let(:store) { local }
+
+ context 'and remote storage is defined' do
+ before do
+ stub_artifacts_object_storage(background_upload: true)
+ end
+
+ it "migrates file to remote storage" do
+ perform
+
+ expect(build.reload.artifacts_file_store).to eq(remote)
+ end
+
+ context 'for artifacts_metadata' do
+ let(:file_field) { :artifacts_metadata }
+
+ it 'migrates metadata to remote storage' do
+ perform
+
+ expect(build.reload.artifacts_metadata_store).to eq(remote)
+ end
+ end
+ end
+ end
+ end
+
+ context 'for job artifacts' do
+ let(:artifact) { create(:ci_job_artifact, :archive) }
+ let(:uploader_class) { JobArtifactUploader }
+ let(:subject_class) { Ci::JobArtifact }
+ let(:file_field) { :file }
+ let(:subject_id) { artifact.id }
+
+ context 'when local storage is used' do
+ let(:store) { local }
+
+ context 'and remote storage is defined' do
+ before do
+ stub_artifacts_object_storage(background_upload: true)
+ end
+
+ it "migrates file to remote storage" do
+ perform
+
+ expect(artifact.reload.file_store).to eq(remote)
+ end
+ end
+ end
+ end
+
+ context 'for uploads' do
+ let!(:project) { create(:project, :with_avatar) }
+ let(:uploader_class) { AvatarUploader }
+ let(:file_field) { :avatar }
+
+ context 'when local storage is used' do
+ let(:store) { local }
+
+ context 'and remote storage is defined' do
+ before do
+ stub_uploads_object_storage(uploader_class, background_upload: true)
+ end
+
+ describe 'supports using the model' do
+ let(:subject_class) { project.class }
+ let(:subject_id) { project.id }
+
+ it "migrates file to remote storage" do
+ perform
+
+ expect(project.reload.avatar.file_storage?).to be_falsey
+ end
+ end
+
+ describe 'supports using the Upload' do
+ let(:subject_class) { Upload }
+ let(:subject_id) { project.avatar.upload.id }
+
+ it "migrates file to remote storage" do
+ perform
+
+ expect(project.reload.avatar.file_storage?).to be_falsey
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
new file mode 100644
index 00000000000..7a7dcb71680
--- /dev/null
+++ b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
@@ -0,0 +1,119 @@
+require 'spec_helper'
+
+describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
+ shared_context 'sanity_check! fails' do
+ before do
+ expect(described_class).to receive(:sanity_check!).and_raise(described_class::SanityCheckError)
+ end
+ end
+
+ let!(:projects) { create_list(:project, 10, :with_avatar) }
+ let(:uploads) { Upload.all }
+ let(:model_class) { Project }
+ let(:mounted_as) { :avatar }
+ let(:to_store) { ObjectStorage::Store::REMOTE }
+
+ before do
+ stub_uploads_object_storage(AvatarUploader)
+ end
+
+ describe '.enqueue!' do
+ def enqueue!
+ described_class.enqueue!(uploads, Project, mounted_as, to_store)
+ end
+
+ it 'is guarded by .sanity_check!' do
+ expect(described_class).to receive(:perform_async)
+ expect(described_class).to receive(:sanity_check!)
+
+ enqueue!
+ end
+
+ context 'sanity_check! fails' do
+ include_context 'sanity_check! fails'
+
+ it 'does not enqueue a job' do
+ expect(described_class).not_to receive(:perform_async)
+
+ expect { enqueue! }.to raise_error(described_class::SanityCheckError)
+ end
+ end
+ end
+
+ describe '.sanity_check!' do
+ shared_examples 'raises a SanityCheckError' do
+ let(:mount_point) { nil }
+
+ it do
+ expect { described_class.sanity_check!(uploads, model_class, mount_point) }
+ .to raise_error(described_class::SanityCheckError)
+ end
+ end
+
+ context 'uploader types mismatch' do
+ let!(:outlier) { create(:upload, uploader: 'FileUploader') }
+
+ include_examples 'raises a SanityCheckError'
+ end
+
+ context 'model types mismatch' do
+ let!(:outlier) { create(:upload, model_type: 'Potato') }
+
+ include_examples 'raises a SanityCheckError'
+ end
+
+ context 'mount point not found' do
+ include_examples 'raises a SanityCheckError' do
+ let(:mount_point) { :potato }
+ end
+ end
+ end
+
+ describe '#perform' do
+ def perform
+ described_class.new.perform(uploads.ids, model_class.to_s, mounted_as, to_store)
+ rescue ObjectStorage::MigrateUploadsWorker::Report::MigrationFailures
+ # swallow
+ end
+
+ shared_examples 'outputs correctly' do |success: 0, failures: 0|
+ total = success + failures
+
+ if success > 0
+ it 'outputs the reports' do
+ expect(Rails.logger).to receive(:info).with(%r{Migrated #{success}/#{total} files})
+
+ perform
+ end
+ end
+
+ if failures > 0
+ it 'outputs upload failures' do
+ expect(Rails.logger).to receive(:warn).with(/Error .* I am a teapot/)
+
+ perform
+ end
+ end
+ end
+
+ it_behaves_like 'outputs correctly', success: 10
+
+ it 'migrates files' do
+ perform
+
+ aggregate_failures do
+ projects.each do |project|
+ expect(project.reload.avatar.upload.local?).to be_falsey
+ end
+ end
+ end
+
+ context 'migration is unsuccessful' do
+ before do
+ allow_any_instance_of(ObjectStorage::Concern).to receive(:migrate!).and_raise(CarrierWave::UploadError, "I am a teapot.")
+ end
+
+ it_behaves_like 'outputs correctly', failures: 10
+ end
+ end
+end
diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb
new file mode 100644
index 00000000000..8899969c178
--- /dev/null
+++ b/spec/workers/project_export_worker_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe ProjectExportWorker do
+ let!(:user) { create(:user) }
+ let!(:project) { create(:project) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ context 'when it succeeds' do
+ it 'calls the ExportService' do
+ expect_any_instance_of(::Projects::ImportExport::ExportService).to receive(:execute)
+
+ subject.perform(user.id, project.id, { 'klass' => 'Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy' })
+ end
+ end
+
+ context 'when it fails' do
+ it 'raises an exception when params are invalid' do
+ expect_any_instance_of(::Projects::ImportExport::ExportService).not_to receive(:execute)
+
+ expect { subject.perform(1234, project.id, {}) }.to raise_exception(ActiveRecord::RecordNotFound)
+ expect { subject.perform(user.id, 1234, {}) }.to raise_exception(ActiveRecord::RecordNotFound)
+ expect { subject.perform(user.id, project.id, { 'klass' => 'Whatever' }) }.to raise_exception(Gitlab::ImportExport::AfterExportStrategyBuilder::StrategyNotFoundError)
+ end
+ end
+ end
+end