summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/runners_controller_spec.rb85
-rw-r--r--spec/controllers/dashboard_controller_spec.rb19
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb19
-rw-r--r--spec/controllers/profiles/notifications_controller_spec.rb45
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb6
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb65
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb10
-rw-r--r--spec/controllers/projects/runners_controller_spec.rb75
-rw-r--r--spec/controllers/uploads_controller_spec.rb22
-rw-r--r--spec/factories/ci/builds.rb12
-rw-r--r--spec/factories/commits.rb10
-rw-r--r--spec/factories/keys.rb7
-rw-r--r--spec/factories/notes.rb6
-rw-r--r--spec/factories/todos.rb4
-rw-r--r--spec/factories/wiki_directories.rb6
-rw-r--r--spec/factories/wiki_pages.rb18
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb19
-rw-r--r--spec/features/boards/boards_spec.rb6
-rw-r--r--spec/features/calendar_spec.rb222
-rw-r--r--spec/features/dashboard/active_tab_spec.rb7
-rw-r--r--spec/features/dashboard/issuables_counter_spec.rb3
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb10
-rw-r--r--spec/features/issuables/issuable_list_spec.rb57
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb33
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb14
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb32
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb3
-rw-r--r--spec/features/merge_requests/widget_spec.rb15
-rw-r--r--spec/features/profiles/preferences_spec.rb29
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb32
-rw-r--r--spec/features/projects/builds_spec.rb6
-rw-r--r--spec/features/projects/main/download_buttons_spec.rb7
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb29
-rw-r--r--spec/features/projects/ref_switcher_spec.rb6
-rw-r--r--spec/fixtures/api/schemas/user/login.json1
-rw-r--r--spec/fixtures/api/schemas/user/public.json16
-rw-r--r--spec/helpers/application_helper_spec.rb5
-rw-r--r--spec/helpers/page_layout_helper_spec.rb12
-rw-r--r--spec/helpers/preferences_helper_spec.rb26
-rw-r--r--spec/helpers/wiki_helper_spec.rb21
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js46
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js13
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_store_spec.js.es64
-rw-r--r--spec/javascripts/dashboard_spec.js.es637
-rw-r--r--spec/javascripts/environments/environment_actions_spec.js.es66
-rw-r--r--spec/javascripts/environments/environment_external_url_spec.js.es64
-rw-r--r--spec/javascripts/environments/environment_item_spec.js.es636
-rw-r--r--spec/javascripts/environments/environment_rollback_spec.js.es68
-rw-r--r--spec/javascripts/environments/environment_spec.js.es697
-rw-r--r--spec/javascripts/environments/environment_stop_spec.js.es64
-rw-r--r--spec/javascripts/environments/environment_table_spec.js.es630
-rw-r--r--spec/javascripts/environments/environments_store_spec.js.es696
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js.es6202
-rw-r--r--spec/javascripts/environments/mock_data.js.es6211
-rw-r--r--spec/javascripts/fixtures/.gitignore1
-rw-r--r--spec/javascripts/fixtures/behaviors/quick_submit.html.haml6
-rw-r--r--spec/javascripts/fixtures/behaviors/requires_input.html.haml18
-rw-r--r--spec/javascripts/fixtures/branches.rb28
-rw-r--r--spec/javascripts/fixtures/dashboard.html.haml45
-rw-r--r--spec/javascripts/fixtures/environments/environments_folder_view.html.haml7
-rw-r--r--spec/javascripts/fixtures/header.html.haml35
-rw-r--r--spec/javascripts/fixtures/merge_request_tabs.html.haml22
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb36
-rw-r--r--spec/javascripts/fixtures/new_branch.html.haml4
-rw-r--r--spec/javascripts/fixtures/todos.json4
-rw-r--r--spec/javascripts/fixtures/todos.rb52
-rw-r--r--spec/javascripts/gl_dropdown_spec.js.es68
-rw-r--r--spec/javascripts/header_spec.js2
-rw-r--r--spec/javascripts/issue_spec.js4
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js.es624
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js44
-rw-r--r--spec/javascripts/new_branch_spec.js4
-rw-r--r--spec/javascripts/project_dashboard_spec.js.es686
-rw-r--r--spec/javascripts/right_sidebar_spec.js4
-rw-r--r--spec/javascripts/test_bundle.js35
-rw-r--r--spec/javascripts/todos_spec.js63
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js.es612
-rw-r--r--spec/lib/additional_email_headers_interceptor_spec.rb12
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb24
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb13
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb23
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb45
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb26
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb76
-rw-r--r--spec/lib/gitlab/database_spec.rb54
-rw-r--r--spec/lib/gitlab/import_export/import_export_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/project.light.json48
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb169
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb40
-rw-r--r--spec/lib/gitlab/other_markup.rb22
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb75
-rw-r--r--spec/lib/gitlab/regex_spec.rb81
-rw-r--r--spec/lib/gitlab/slash_commands/extractor_spec.rb8
-rw-r--r--spec/lib/gitlab/themes_spec.rb48
-rw-r--r--spec/models/ci/runner_spec.rb21
-rw-r--r--spec/models/project_services/chat_message/build_message_spec.rb28
-rw-r--r--spec/models/project_spec.rb39
-rw-r--r--spec/models/repository_spec.rb11
-rw-r--r--spec/models/user_spec.rb33
-rw-r--r--spec/models/wiki_directory_spec.rb44
-rw-r--r--spec/models/wiki_page_spec.rb118
-rw-r--r--spec/requests/api/branches_spec.rb13
-rw-r--r--spec/requests/api/commit_statuses_spec.rb2
-rw-r--r--spec/requests/api/commits_spec.rb34
-rw-r--r--spec/requests/api/fork_spec.rb134
-rw-r--r--spec/requests/api/groups_spec.rb20
-rw-r--r--spec/requests/api/members_spec.rb4
-rw-r--r--spec/requests/api/milestones_spec.rb36
-rw-r--r--spec/requests/api/namespaces_spec.rb6
-rw-r--r--spec/requests/api/projects_spec.rb385
-rw-r--r--spec/requests/api/runners_spec.rb7
-rw-r--r--spec/requests/api/templates_spec.rb58
-rw-r--r--spec/requests/api/v3/members_spec.rb342
-rw-r--r--spec/requests/api/v3/projects_spec.rb1
-rw-r--r--spec/requests/api/v3/templates_spec.rb203
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb33
-rw-r--r--spec/serializers/environment_serializer_spec.rb11
-rw-r--r--spec/services/ci/update_runner_service_spec.rb41
-rw-r--r--spec/services/groups/destroy_service_spec.rb19
-rw-r--r--spec/services/issues/build_service_spec.rb11
-rw-r--r--spec/services/issues/create_service_spec.rb1
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb29
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb41
-rw-r--r--spec/services/notification_service_spec.rb59
-rw-r--r--spec/services/tags/create_service_spec.rb (renamed from spec/services/create_tag_service_spec.rb)2
-rw-r--r--spec/services/tags/destroy_service_spec.rb (renamed from spec/services/delete_tag_service_spec.rb)2
-rw-r--r--spec/services/todo_service_spec.rb206
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb36
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb21
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb37
-rw-r--r--spec/support/gitlab_stubs/session.json4
-rw-r--r--spec/support/gitlab_stubs/user.json4
-rw-r--r--spec/support/issuables_list_metadata_shared_examples.rb35
-rw-r--r--spec/support/services/issuable_create_service_slash_commands_shared_examples.rb2
-rw-r--r--spec/views/projects/_home_panel.html.haml_spec.rb38
-rw-r--r--spec/views/projects/notes/_form.html.haml_spec.rb4
137 files changed, 3949 insertions, 1355 deletions
diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
new file mode 100644
index 00000000000..b5fe40d0510
--- /dev/null
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe Admin::RunnersController do
+ let(:runner) { create(:ci_runner) }
+
+ before do
+ sign_in(create(:admin))
+ end
+
+ describe '#index' do
+ it 'lists all runners' do
+ get :index
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ describe '#show' do
+ it 'shows a particular runner' do
+ get :show, id: runner.id
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'shows 404 for unknown runner' do
+ get :show, id: 0
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe '#update' do
+ it 'updates the runner and ticks the queue' do
+ new_desc = runner.description.swapcase
+
+ expect do
+ post :update, id: runner.id, runner: { description: new_desc }
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.description).to eq(new_desc)
+ end
+ end
+
+ describe '#destroy' do
+ it 'destroys the runner' do
+ delete :destroy, id: runner.id
+
+ expect(response).to have_http_status(302)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ end
+ end
+
+ describe '#resume' do
+ it 'marks the runner as active and ticks the queue' do
+ runner.update(active: false)
+
+ expect do
+ post :resume, id: runner.id
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(true)
+ end
+ end
+
+ describe '#pause' do
+ it 'marks the runner as inactive and ticks the queue' do
+ runner.update(active: true)
+
+ expect do
+ post :pause, id: runner.id
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(false)
+ end
+ end
+end
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
new file mode 100644
index 00000000000..566d8515198
--- /dev/null
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe DashboardController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ describe 'GET issues' do
+ it_behaves_like 'issuables list meta-data', :issue, :issues
+ end
+
+ describe 'GET merge requests' do
+ it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
+ end
+end
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 6bcfae0fc13..f7219690722 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -42,10 +42,9 @@ describe Profiles::KeysController do
end
describe "user with keys" do
- before do
- user.keys << create(:key)
- user.keys << create(:another_key)
- end
+ let!(:key) { create(:key, user: user) }
+ let!(:another_key) { create(:another_key, user: user) }
+ let!(:deploy_key) { create(:deploy_key, user: user) }
it "does generally work" do
get :get_keys, username: user.username
@@ -53,16 +52,16 @@ describe Profiles::KeysController do
expect(response).to be_success
end
- it "renders all keys separated with a new line" do
+ it "renders all non deploy keys separated with a new line" do
get :get_keys, username: user.username
- expect(response.body).not_to eq("")
+ expect(response.body).not_to eq('')
expect(response.body).to eq(user.all_ssh_keys.join("\n"))
- # Unique part of key 1
- expect(response.body).to match(/PWx6WM4lhHNedGfBpPJNPpZ/)
- # Key 2
- expect(response.body).to match(/AQDmTillFzNTrrGgwaCKaSj/)
+ expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
+ expect(response.body).to include(another_key.key)
+
+ expect(response.body).not_to include(deploy_key.key)
end
it "does not render the comment of the key" do
diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb
new file mode 100644
index 00000000000..58caf7999cf
--- /dev/null
+++ b/spec/controllers/profiles/notifications_controller_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Profiles::NotificationsController do
+ let(:user) do
+ create(:user) do |user|
+ user.emails.create(email: 'original@example.com')
+ user.emails.create(email: 'new@example.com')
+ user.update(notification_email: 'original@example.com')
+ user.save!
+ end
+ end
+
+ describe 'GET show' do
+ it 'renders' do
+ sign_in(user)
+
+ get :show
+
+ expect(response).to render_template :show
+ end
+ end
+
+ describe 'POST update' do
+ it 'updates only permitted attributes' do
+ sign_in(user)
+
+ put :update, user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true }
+
+ user.reload
+ expect(user.notification_email).to eq('new@example.com')
+ expect(user.notified_of_own_activity).to eq(true)
+ expect(user.admin).to eq(false)
+ expect(controller).to set_flash[:notice].to('Notification settings saved')
+ end
+
+ it 'shows an error message if the params are invalid' do
+ sign_in(user)
+
+ put :update, user: { notification_email: '' }
+
+ expect(user.reload.notification_email).to eq('original@example.com')
+ expect(controller).to set_flash[:alert].to('Failed to save new settings')
+ end
+ end
+end
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index 8f02003992a..7b3aa0491c7 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -25,8 +25,7 @@ describe Profiles::PreferencesController do
def go(params: {}, format: :js)
params.reverse_merge!(
color_scheme_id: '1',
- dashboard: 'stars',
- theme_id: '1'
+ dashboard: 'stars'
)
patch :update, user: params, format: format
@@ -41,8 +40,7 @@ describe Profiles::PreferencesController do
it "changes the user's preferences" do
prefs = {
color_scheme_id: '1',
- dashboard: 'stars',
- theme_id: '2'
+ dashboard: 'stars'
}.with_indifferent_access
expect(user).to receive(:update_attributes).with(prefs)
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 7ac1d62d1b1..84d119f1867 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -3,9 +3,12 @@ require 'spec_helper'
describe Projects::EnvironmentsController do
include ApiHelpers
- let(:environment) { create(:environment) }
- let(:project) { environment.project }
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+
+ let(:environment) do
+ create(:environment, name: 'production', project: project)
+ end
before do
project.team << [user, :master]
@@ -22,14 +25,58 @@ describe Projects::EnvironmentsController do
end
end
- context 'when requesting JSON response' do
- it 'responds with correct JSON' do
- get :index, environment_params(format: :json)
+ context 'when requesting JSON response for folders' do
+ before do
+ create(:environment, project: project,
+ name: 'staging/review-1',
+ state: :available)
+
+ create(:environment, project: project,
+ name: 'staging/review-2',
+ state: :available)
+
+ create(:environment, project: project,
+ name: 'staging/review-3',
+ state: :stopped)
+ end
+
+ let(:environments) { json_response['environments'] }
+
+ context 'when requesting available environments scope' do
+ before do
+ get :index, environment_params(format: :json, scope: :available)
+ end
+
+ it 'responds with a payload describing available environments' do
+ expect(environments.count).to eq 2
+ expect(environments.first['name']).to eq 'production'
+ expect(environments.second['name']).to eq 'staging'
+ expect(environments.second['size']).to eq 2
+ expect(environments.second['latest']['name']).to eq 'staging/review-2'
+ end
- first_environment = json_response.first
+ it 'contains values describing environment scopes sizes' do
+ expect(json_response['available_count']).to eq 3
+ expect(json_response['stopped_count']).to eq 1
+ end
+ end
- expect(first_environment).not_to be_empty
- expect(first_environment['name']). to eq environment.name
+ context 'when requesting stopped environments scope' do
+ before do
+ get :index, environment_params(format: :json, scope: :stopped)
+ end
+
+ it 'responds with a payload describing stopped environments' do
+ expect(environments.count).to eq 1
+ expect(environments.first['name']).to eq 'staging'
+ expect(environments.first['size']).to eq 1
+ expect(environments.first['latest']['name']).to eq 'staging/review-3'
+ end
+
+ it 'contains values describing environment scopes sizes' do
+ expect(json_response['available_count']).to eq 3
+ expect(json_response['stopped_count']).to eq 1
+ end
end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 4b89381eb96..e576bf9ef79 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -24,6 +24,8 @@ describe Projects::IssuesController do
project.team << [user, :developer]
end
+ it_behaves_like "issuables list meta-data", :issue
+
it "returns index" do
get :index, namespace_id: project.namespace.path, project_id: project.path
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 63780802cfa..f84f922ba5e 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -147,6 +147,8 @@ describe Projects::MergeRequestsController do
end
describe 'GET index' do
+ let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+
def get_merge_requests(page = nil)
get :index,
namespace_id: project.namespace.to_param,
@@ -154,6 +156,8 @@ describe Projects::MergeRequestsController do
state: 'opened', page: page.to_param
end
+ it_behaves_like "issuables list meta-data", :merge_request
+
context 'when page param' do
let(:last_page) { project.merge_requests.page().total_pages }
let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
@@ -1139,15 +1143,15 @@ describe Projects::MergeRequestsController do
end
end
- context 'when no special status for MR' do
+ context 'when MR does not have special state' do
let(:merge_request) { create(:merge_request, source_project: project) }
it 'returns an OK response' do
expect(response).to have_http_status(:ok)
end
- it 'sets status to nil' do
- expect(assigns(:status)).to be_nil
+ it 'sets status to success' do
+ expect(assigns(:status)).to eq(:success)
expect(response).to render_template('merge')
end
end
diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb
new file mode 100644
index 00000000000..0fa249e4405
--- /dev/null
+++ b/spec/controllers/projects/runners_controller_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Projects::RunnersController do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:runner) { create(:ci_runner) }
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: runner
+ }
+ end
+
+ before do
+ sign_in(user)
+ project.add_master(user)
+ project.runners << runner
+ end
+
+ describe '#update' do
+ it 'updates the runner and ticks the queue' do
+ new_desc = runner.description.swapcase
+
+ expect do
+ post :update, params.merge(runner: { description: new_desc } )
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.description).to eq(new_desc)
+ end
+ end
+
+ describe '#destroy' do
+ it 'destroys the runner' do
+ delete :destroy, params
+
+ expect(response).to have_http_status(302)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ end
+ end
+
+ describe '#resume' do
+ it 'marks the runner as active and ticks the queue' do
+ runner.update(active: false)
+
+ expect do
+ post :resume, params
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(true)
+ end
+ end
+
+ describe '#pause' do
+ it 'marks the runner as inactive and ticks the queue' do
+ runner.update(active: true)
+
+ expect do
+ post :pause, params
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(false)
+ end
+ end
+end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 570d9fa43f8..c9584ddf18c 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -4,6 +4,28 @@ describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
describe "GET show" do
+ context 'Content-Disposition security measures' do
+ let(:project) { create(:empty_project, :public) }
+
+ context 'for PNG files' do
+ it 'returns Content-Disposition: inline' do
+ note = create(:note, :with_attachment, project: project)
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+
+ expect(response['Content-Disposition']).to start_with('inline;')
+ end
+ end
+
+ context 'for SVG files' do
+ it 'returns Content-Disposition: attachment' do
+ note = create(:note, :with_svg_attachment, project: project)
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.svg'
+
+ expect(response['Content-Disposition']).to start_with('attachment;')
+ end
+ end
+ end
+
context "when viewing a user avatar" do
context "when signed in" do
before do
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 4f671091afc..a90534d10ba 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -139,5 +139,17 @@ FactoryGirl.define do
build.save!
end
end
+
+ trait :with_commit do
+ after(:build) do |build|
+ allow(build).to receive(:commit).and_return build(:commit, :without_author)
+ end
+ end
+
+ trait :with_commit_and_author do
+ after(:build) do |build|
+ allow(build).to receive(:commit).and_return build(:commit)
+ end
+ end
end
end
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index ac6eb0a7897..89e260cf65b 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -8,5 +8,15 @@ FactoryGirl.define do
initialize_with do
new(git_commit, project)
end
+
+ after(:build) do |commit|
+ allow(commit).to receive(:author).and_return build(:author)
+ end
+
+ trait :without_author do
+ after(:build) do |commit|
+ allow(commit).to receive(:author).and_return nil
+ end
+ end
end
end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index d69c5b38d0a..dd93b439b2b 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -2,10 +2,13 @@ FactoryGirl.define do
factory :key do
title
key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com"
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com'
end
factory :deploy_key, class: 'DeployKey' do
+ key do
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O96x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5/jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaCrzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy05qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz'
+ end
end
factory :personal_key do
@@ -14,7 +17,7 @@ FactoryGirl.define do
factory :another_key do
key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ"
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ'
end
factory :another_deploy_key, class: 'DeployKey' do
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index a21da7074f9..5c50cd7f4ad 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -97,7 +97,11 @@ FactoryGirl.define do
end
trait :with_attachment do
- attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
+ attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") }
+ end
+
+ trait :with_svg_attachment do
+ attachment { fixture_file_upload(Rails.root + "spec/fixtures/unsanitized.svg", "image/svg+xml") }
end
end
end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 275561502cd..b4e4cd97780 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -14,6 +14,10 @@ FactoryGirl.define do
action { Todo::MENTIONED }
end
+ trait :directly_addressed do
+ action { Todo::DIRECTLY_ADDRESSED }
+ end
+
trait :on_commit do
commit_id RepoHelpers.sample_commit.id
target_type "Commit"
diff --git a/spec/factories/wiki_directories.rb b/spec/factories/wiki_directories.rb
new file mode 100644
index 00000000000..3f3c864ac2b
--- /dev/null
+++ b/spec/factories/wiki_directories.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :wiki_directory do
+ slug '/path_up_to/dir'
+ initialize_with { new(slug) }
+ end
+end
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index efa6cbe5bb1..4105f59e289 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -2,8 +2,26 @@ require 'ostruct'
FactoryGirl.define do
factory :wiki_page do
+ transient do
+ attrs do
+ {
+ title: 'Title',
+ content: 'Content for wiki page',
+ format: 'markdown'
+ }
+ end
+ end
+
page { OpenStruct.new(url_path: 'some-name') }
association :wiki, factory: :project_wiki, strategy: :build
initialize_with { new(wiki, page, true) }
+
+ before(:create) do |page, evaluator|
+ page.attributes = evaluator.attrs
+ end
+
+ to_create do |page|
+ page.create
+ end
end
end
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 7fcfe5a54c7..340884fc986 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -30,5 +30,24 @@ describe "Admin::AbuseReports", feature: true, js: true do
end
end
end
+
+ describe 'if a many users have been reported for abuse' do
+ let(:report_count) { AbuseReport.default_per_page + 3 }
+
+ before do
+ report_count.times do
+ create(:abuse_report, user: create(:user))
+ end
+ end
+
+ describe 'in the abuse report view' do
+ it 'presents information about abuse report' do
+ visit admin_abuse_reports_path
+
+ expect(page).to have_selector('.pagination')
+ expect(page).to have_selector('.pagination .page', count: (report_count.to_f / AbuseReport.default_per_page).ceil)
+ end
+ end
+ end
end
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 7225f38b7e5..1b25b51cfb2 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -28,6 +28,12 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content('Welcome to your Issue Board!')
end
+ it 'disables add issues button by default' do
+ button = page.find('.issue-boards-search button', text: 'Add issues')
+
+ expect(button[:disabled]).to eq true
+ end
+
it 'hides the blank state when clicking nevermind button' do
page.within(find('.board-blank-state')) do
click_button("Nevermind, I'll use my own")
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 3e0b6364e0d..35d090c4b7f 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -1,9 +1,11 @@
require 'spec_helper'
-feature 'Contributions Calendar', js: true, feature: true do
+feature 'Contributions Calendar', :feature, :js do
include WaitForAjax
+ let(:user) { create(:user) }
let(:contributed_project) { create(:project, :public) }
+ let(:issue_note) { create(:note, project: contributed_project) }
# Ex/ Sunday Jan 1, 2016
date_format = '%A %b %-d, %Y'
@@ -12,31 +14,31 @@ feature 'Contributions Calendar', js: true, feature: true do
issue_params = { title: issue_title }
def get_cell_color_selector(contributions)
- contribution_cell = '.user-contrib-cell'
- activity_colors = Array['#ededed', '#acd5f2', '#7fa8c9', '#527ba0', '#254e77']
- activity_colors_index = 0
-
- if contributions > 0 && contributions < 10
- activity_colors_index = 1
- elsif contributions >= 10 && contributions < 20
- activity_colors_index = 2
- elsif contributions >= 20 && contributions < 30
- activity_colors_index = 3
- elsif contributions >= 30
- activity_colors_index = 4
- end
+ activity_colors = %w[#ededed #acd5f2 #7fa8c9 #527ba0 #254e77]
+ # We currently don't actually test the cases with contributions >= 20
+ activity_colors_index =
+ if contributions > 0 && contributions < 10
+ 1
+ elsif contributions >= 10 && contributions < 20
+ 2
+ elsif contributions >= 20 && contributions < 30
+ 3
+ elsif contributions >= 30
+ 4
+ else
+ 0
+ end
- "#{contribution_cell}[fill='#{activity_colors[activity_colors_index]}']"
+ ".user-contrib-cell[fill='#{activity_colors[activity_colors_index]}']"
end
def get_cell_date_selector(contributions, date)
- contribution_text = 'No contributions'
-
- if contributions === 1
- contribution_text = '1 contribution'
- elsif contributions > 1
- contribution_text = "#{contributions} contributions"
- end
+ contribution_text =
+ if contributions.zero?
+ 'No contributions'
+ else
+ "#{contributions} #{'contribution'.pluralize(contributions)}"
+ end
"#{get_cell_color_selector(contributions)}[data-original-title='#{contribution_text}<br />#{date}']"
end
@@ -45,129 +47,155 @@ feature 'Contributions Calendar', js: true, feature: true do
push_params = {
project: contributed_project,
action: Event::PUSHED,
- author_id: @user.id,
+ author_id: user.id,
data: { commit_count: 3 }
}
Event.create(push_params)
end
- def get_first_cell_content
- find('.user-calendar-activities').text
- end
+ def note_comment_contribution
+ note_comment_params = {
+ project: contributed_project,
+ action: Event::COMMENTED,
+ target: issue_note,
+ author_id: user.id
+ }
- before do
- login_as :user
- visit @user.username
- wait_for_ajax
+ Event.create(note_comment_params)
end
- it 'displays calendar', js: true do
- expect(page).to have_css('.js-contrib-calendar')
+ def selected_day_activities
+ find('.user-calendar-activities').text
end
- describe 'select calendar day', js: true do
- let(:cells) { page.all('.user-contrib-cell') }
- let(:first_cell_content_before) { get_first_cell_content }
+ before do
+ login_as user
+ end
+ describe 'calendar day selection' do
before do
- cells[0].click
+ visit user.username
wait_for_ajax
- first_cell_content_before
end
- it 'displays calendar day activities', js: true do
- expect(get_first_cell_content).not_to eq('')
+ it 'displays calendar' do
+ expect(page).to have_css('.js-contrib-calendar')
end
- describe 'select another calendar day', js: true do
+ describe 'select calendar day' do
+ let(:cells) { page.all('.user-contrib-cell') }
+
before do
- cells[1].click
+ cells[0].click
wait_for_ajax
+ @first_day_activities = selected_day_activities
end
- it 'displays different calendar day activities', js: true do
- expect(get_first_cell_content).not_to eq(first_cell_content_before)
+ it 'displays calendar day activities' do
+ expect(selected_day_activities).not_to be_empty
end
- end
- describe 'deselect calendar day', js: true do
- before do
- cells[0].click
- wait_for_ajax
+ describe 'select another calendar day' do
+ before do
+ cells[1].click
+ wait_for_ajax
+ end
+
+ it 'displays different calendar day activities' do
+ expect(selected_day_activities).not_to eq(@first_day_activities)
+ end
end
- it 'hides calendar day activities', js: true do
- expect(get_first_cell_content).to eq('')
+ describe 'deselect calendar day' do
+ before do
+ cells[0].click
+ wait_for_ajax
+ end
+
+ it 'hides calendar day activities' do
+ expect(selected_day_activities).to be_empty
+ end
end
end
end
- describe '1 calendar activity' do
- before do
- Issues::CreateService.new(contributed_project, @user, issue_params).execute
- visit @user.username
- wait_for_ajax
+ describe 'calendar daily activities' do
+ shared_context 'visit user page' do
+ before do
+ visit user.username
+ wait_for_ajax
+ end
end
- it 'displays calendar activity log', js: true do
- expect(find('.content_list .event-note')).to have_content issue_title
- end
+ shared_examples 'a day with activity' do |contribution_count:|
+ include_context 'visit user page'
- it 'displays calendar activity square color for 1 contribution', js: true do
- expect(page).to have_selector(get_cell_color_selector(1), count: 1)
- end
+ it 'displays calendar activity square color for 1 contribution' do
+ expect(page).to have_selector(get_cell_color_selector(contribution_count), count: 1)
+ end
- it 'displays calendar activity square on the correct date', js: true do
- today = Date.today.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ it 'displays calendar activity square on the correct date' do
+ today = Date.today.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(contribution_count, today), count: 1)
+ end
end
- end
- describe '10 calendar activities' do
- before do
- (0..9).each do |i|
- push_code_contribution()
+ describe '1 issue creation calendar activity' do
+ before do
+ Issues::CreateService.new(contributed_project, user, issue_params).execute
end
- visit @user.username
- wait_for_ajax
- end
+ it_behaves_like 'a day with activity', contribution_count: 1
- it 'displays calendar activity square color for 10 contributions', js: true do
- expect(page).to have_selector(get_cell_color_selector(10), count: 1)
- end
+ describe 'issue title is shown on activity page' do
+ include_context 'visit user page'
- it 'displays calendar activity square on the correct date', js: true do
- today = Date.today.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(10, today), count: 1)
+ it 'displays calendar activity log' do
+ expect(find('.content_list .event-note')).to have_content issue_title
+ end
+ end
end
- end
- describe 'calendar activity on two days' do
- before do
- push_code_contribution()
-
- Timecop.freeze(Date.yesterday)
- Issues::CreateService.new(contributed_project, @user, issue_params).execute
- Timecop.return
+ describe '1 comment calendar activity' do
+ before do
+ note_comment_contribution
+ end
- visit @user.username
- wait_for_ajax
+ it_behaves_like 'a day with activity', contribution_count: 1
end
- it 'displays calendar activity squares for both days', js: true do
- expect(page).to have_selector(get_cell_color_selector(1), count: 2)
- end
+ describe '10 calendar activities' do
+ before do
+ 10.times { push_code_contribution }
+ end
- it 'displays calendar activity square for yesterday', js: true do
- yesterday = Date.yesterday.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
+ it_behaves_like 'a day with activity', contribution_count: 10
end
- it 'displays calendar activity square for today', js: true do
- today = Date.today.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ describe 'calendar activity on two days' do
+ before do
+ push_code_contribution
+
+ Timecop.freeze(Date.yesterday) do
+ Issues::CreateService.new(contributed_project, user, issue_params).execute
+ end
+ end
+ include_context 'visit user page'
+
+ it 'displays calendar activity squares for both days' do
+ expect(page).to have_selector(get_cell_color_selector(1), count: 2)
+ end
+
+ it 'displays calendar activity square for yesterday' do
+ yesterday = Date.yesterday.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
+ end
+
+ it 'displays calendar activity square for today' do
+ today = Date.today.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ end
end
end
end
diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb
index 7d59fcac517..ae750be4d4a 100644
--- a/spec/features/dashboard/active_tab_spec.rb
+++ b/spec/features/dashboard/active_tab_spec.rb
@@ -1,14 +1,15 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Active Tab', feature: true do
+RSpec.describe 'Dashboard Active Tab', js: true, feature: true do
before do
login_as :user
end
shared_examples 'page has active tab' do |title|
it "#{title} tab" do
- expect(page).to have_selector('.nav-sidebar li.active', count: 1)
- expect(find('.nav-sidebar li.active')).to have_content(title)
+ find('.global-dropdown-toggle').trigger('click')
+ expect(page).to have_selector('.global-dropdown-menu li.active', count: 1)
+ expect(find('.global-dropdown-menu li.active')).to have_content(title)
end
end
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index 41dcfe439c2..603076d7d37 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -36,7 +36,8 @@ describe 'Navigation bar counter', feature: true, js: true, caching: true do
def expect_counters(issuable_type, count)
dashboard_count = find('li.active span.badge')
- nav_count = find(".dashboard-shortcuts-#{issuable_type} span.count")
+ find('.global-dropdown-toggle').click
+ nav_count = find(".dashboard-shortcuts-#{issuable_type} span.badge")
expect(nav_count).to have_content(count)
expect(dashboard_count).to have_content(count)
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index d9be4e5dbdd..62a2c54c94c 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -10,20 +10,20 @@ feature 'Dashboard shortcuts', feature: true, js: true do
find('body').native.send_key('g')
find('body').native.send_key('p')
- ensure_active_main_tab('Projects')
+ check_page_title('Projects')
find('body').native.send_key('g')
find('body').native.send_key('i')
- ensure_active_main_tab('Issues')
+ check_page_title('Issues')
find('body').native.send_key('g')
find('body').native.send_key('m')
- ensure_active_main_tab('Merge Requests')
+ check_page_title('Merge Requests')
end
- def ensure_active_main_tab(content)
- expect(find('.nav-sidebar li.active')).to have_content(content)
+ def check_page_title(title)
+ expect(find('.header-content .title')).to have_content(title)
end
end
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
new file mode 100644
index 00000000000..e31bc40adc3
--- /dev/null
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -0,0 +1,57 @@
+require 'rails_helper'
+
+describe 'issuable list', feature: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ issuable_types = [:issue, :merge_request]
+
+ before do
+ project.add_user(user, :developer)
+ login_as(user)
+ issuable_types.each { |type| create_issuables(type) }
+ end
+
+ issuable_types.each do |issuable_type|
+ it "avoids N+1 database queries for #{issuable_type.to_s.humanize.pluralize}" do
+ control_count = ActiveRecord::QueryRecorder.new { visit_issuable_list(issuable_type) }.count
+
+ create_issuables(issuable_type)
+
+ expect { visit_issuable_list(issuable_type) }.not_to exceed_query_limit(control_count)
+ end
+
+ it "counts upvotes, downvotes and notes count for each #{issuable_type.to_s.humanize}" do
+ visit_issuable_list(issuable_type)
+
+ expect(first('.fa-thumbs-up').find(:xpath, '..')).to have_content(1)
+ expect(first('.fa-thumbs-down').find(:xpath, '..')).to have_content(1)
+ expect(first('.fa-comments').find(:xpath, '..')).to have_content(2)
+ end
+ end
+
+ def visit_issuable_list(issuable_type)
+ if issuable_type == :issue
+ visit namespace_project_issues_path(project.namespace, project)
+ else
+ visit namespace_project_merge_requests_path(project.namespace, project)
+ end
+ end
+
+ def create_issuables(issuable_type)
+ 3.times do
+ if issuable_type == :issue
+ issuable = create(:issue, project: project, author: user)
+ else
+ issuable = create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: FFaker::Name.name)
+ end
+
+ 2.times do
+ create(:note_on_issue, noteable: issuable, project: project, note: 'Test note')
+ end
+
+ create(:award_emoji, :downvote, awardable: issuable)
+ create(:award_emoji, :upvote, awardable: issuable)
+ end
+ end
+end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 6f7046c8461..64f448a83b7 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -113,13 +113,11 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid author' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple authors' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
@@ -158,8 +156,7 @@ describe 'Filter issues', js: true, feature: true do
end
it 'sorting' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
@@ -182,13 +179,11 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid assignee' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple assignees' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
@@ -228,8 +223,7 @@ describe 'Filter issues', js: true, feature: true do
context 'sorting' do
it 'sorts' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
end
@@ -253,8 +247,7 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid label' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple labels' do
@@ -429,8 +422,7 @@ describe 'Filter issues', js: true, feature: true do
context 'sorting' do
it 'sorts' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
end
@@ -456,13 +448,11 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid milestones' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple milestones' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by milestone containing special characters' do
@@ -523,8 +513,7 @@ describe 'Filter issues', js: true, feature: true do
context 'sorting' do
it 'sorts' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 93139dc9e94..7135565294b 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -182,6 +182,20 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).not_to have_selector('.atwho-view')
end
+ it 'triggers autocomplete after selecting a slash command' do
+ note = find('#note_note')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('')
+ note.native.send_keys('/as')
+ note.click
+ end
+
+ find('.atwho-view li', text: '/assign').native.send_keys(:tab)
+
+ user_item = find('.atwho-view li', text: user.username)
+ expect(user_item).to have_content(user.username)
+ end
+
def expect_to_wrap(should_wrap, item, note, value)
expect(item).to have_content(value)
expect(item).not_to have_content("\"#{value}\"")
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index c73065cdce1..eafcab6a0d7 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -10,10 +10,12 @@ feature 'Merge Request closing issues message', feature: true do
:merge_request,
:simple,
source_project: project,
- description: merge_request_description
+ description: merge_request_description,
+ title: merge_request_title
)
end
let(:merge_request_description) { 'Merge Request Description' }
+ let(:merge_request_title) { 'Merge Request Title' }
before do
project.team << [user, :master]
@@ -45,8 +47,32 @@ feature 'Merge Request closing issues message', feature: true do
end
end
- context 'closing some issues and mentioning, but not closing, others' do
- let(:merge_request_description) { "Description\n\ncloses #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
+ context 'closing some issues in title and mentioning, but not closing, others' do
+ let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
+
+ it 'does not display closing issue message' do
+ expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.")
+ end
+ end
+
+ context 'closing issues using title but not mentioning any other issue' do
+ let(:merge_request_title) { "closing #{issue_1.to_reference}, #{issue_2.to_reference}" }
+
+ it 'does not display closing issue message' do
+ expect(page).to have_content("Accepting this merge request will close issues #{issue_1.to_reference} and #{issue_2.to_reference}")
+ end
+ end
+
+ context 'mentioning issues using title but not closing them' do
+ let(:merge_request_title) { "Refers to #{issue_1.to_reference} and #{issue_2.to_reference}" }
+
+ it 'does not display closing issue message' do
+ expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.")
+ end
+ end
+
+ context 'closing some issues using title and mentioning, but not closing, others' do
+ let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
it 'does not display closing issue message' do
expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.")
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index e853fb7e016..0832a3656a8 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
feature 'Create New Merge Request', feature: true, js: true do
+ include WaitForVueResource
+
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
@@ -99,6 +101,7 @@ feature 'Create New Merge Request', feature: true, js: true do
page.within('.merge-request') do
click_link 'Pipelines'
+ wait_for_vue_resource
expect(page).to have_content "##{pipeline.id}"
end
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 957e913bf95..4ad944366c8 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -52,4 +52,19 @@ describe 'Merge request', :feature, :js do
end
end
end
+
+ context 'merge error' do
+ before do
+ allow_any_instance_of(Repository).to receive(:merge).and_return(false)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ click_button 'Accept Merge Request'
+ wait_for_ajax
+ end
+
+ it 'updates the MR widget' do
+ page.within('.mr-widget-body') do
+ expect(page).to have_content('Conflicts detected during merge')
+ end
+ end
+ end
end
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index a6b841c0210..15c8677fcd3 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -8,35 +8,6 @@ describe 'Profile > Preferences', feature: true do
visit profile_preferences_path
end
- describe 'User changes their application theme', js: true do
- let(:default) { Gitlab::Themes.default }
- let(:theme) { Gitlab::Themes.by_id(5) }
-
- it 'creates a flash message' do
- choose "user_theme_id_#{theme.id}"
-
- expect_preferences_saved_message
- end
-
- it 'updates their preference' do
- choose "user_theme_id_#{theme.id}"
-
- allowing_for_delay do
- visit page.current_path
- expect(page).to have_checked_field("user_theme_id_#{theme.id}")
- end
- end
-
- it 'reflects the changes immediately' do
- expect(page).to have_selector("body.#{default.css_class}")
-
- choose "user_theme_id_#{theme.id}"
-
- expect(page).not_to have_selector("body.#{default.css_class}")
- expect(page).to have_selector("body.#{theme.css_class}")
- end
- end
-
describe 'User changes their syntax highlighting theme', js: true do
it 'creates a flash message' do
choose 'user_color_scheme_id_5'
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
new file mode 100644
index 00000000000..e05fbb3715c
--- /dev/null
+++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+feature 'Profile > Notifications > User changes notified_of_own_activity setting', feature: true, js: true do
+ let(:user) { create(:user) }
+
+ before do
+ login_as(user)
+ end
+
+ scenario 'User opts into receiving notifications about their own activity' do
+ visit profile_notifications_path
+
+ expect(page).not_to have_checked_field('user[notified_of_own_activity]')
+
+ check 'user[notified_of_own_activity]'
+
+ expect(page).to have_content('Notification settings saved')
+ expect(page).to have_checked_field('user[notified_of_own_activity]')
+ end
+
+ scenario 'User opts out of receiving notifications about their own activity' do
+ user.update!(notified_of_own_activity: true)
+ visit profile_notifications_path
+
+ expect(page).to have_checked_field('user[notified_of_own_activity]')
+
+ uncheck 'user[notified_of_own_activity]'
+
+ expect(page).to have_content('Notification settings saved')
+ expect(page).not_to have_checked_field('user[notified_of_own_activity]')
+ end
+end
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index f7e0115643e..2116721b224 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -109,6 +109,10 @@ feature 'Builds', :feature do
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.git_author_name
end
+
+ it 'shows active build' do
+ expect(page).to have_selector('.build-job.active')
+ end
end
context "Job from other project" do
@@ -271,7 +275,7 @@ feature 'Builds', :feature do
let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
- it 'shows a link to lastest deployment' do
+ it 'shows a link to latest deployment' do
visit namespace_project_build_path(project.namespace, project, build)
expect(page).to have_link('latest deployment')
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
index 227ccf9459c..02198ff3e41 100644
--- a/spec/features/projects/main/download_buttons_spec.rb
+++ b/spec/features/projects/main/download_buttons_spec.rb
@@ -39,6 +39,13 @@ feature 'Download buttons in project main page', feature: true do
expect(page).to have_link "Download '#{build.name}'", href: href
end
+
+ scenario 'download links have download attribute' do
+ expect(page).to have_selector('a', text: 'Download')
+ page.all('a', text: 'Download').each do |link|
+ expect(link[:download]).to eq ''
+ end
+ end
end
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 6555b2fc6c1..8d1214dedb4 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -218,6 +218,14 @@ describe 'Pipelines', :feature, :js do
expect(page).to have_link(with_artifacts.name)
end
+
+ it 'has download attribute on download links' do
+ find('.js-pipeline-dropdown-download').click
+ expect(page).to have_selector('a', text: 'Download')
+ page.all('.build-artifacts a', text: 'Download').each do |link|
+ expect(link[:download]).to eq ''
+ end
+ end
end
context 'with artifacts expired' do
@@ -277,6 +285,27 @@ describe 'Pipelines', :feature, :js do
end
end
end
+
+ context 'with pagination' do
+ before do
+ allow(Ci::Pipeline).to receive(:default_per_page).and_return(1)
+ create(:ci_empty_pipeline, project: project)
+ end
+
+ it 'should render pagination' do
+ visit namespace_project_pipelines_path(project.namespace, project)
+ wait_for_vue_resource
+
+ expect(page).to have_selector('.gl-pagination')
+ end
+
+ it 'should render second page of pipelines' do
+ visit namespace_project_pipelines_path(project.namespace, project, page: '2')
+ wait_for_vue_resource
+
+ expect(page).to have_selector('.gl-pagination .page', count: 2)
+ end
+ end
end
describe 'POST /:project/pipelines' do
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 38fe2d92885..4eafac1acd8 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -20,9 +20,9 @@ feature 'Ref switcher', feature: true, js: true do
input.set 'binary'
wait_for_ajax
- input.native.send_keys :down
- input.native.send_keys :down
- input.native.send_keys :enter
+ page.within '.dropdown-content ul' do
+ input.native.send_keys :enter
+ end
end
expect(page).to have_title 'binary-encoding'
diff --git a/spec/fixtures/api/schemas/user/login.json b/spec/fixtures/api/schemas/user/login.json
index e6c1d9c9d84..6181b3ccc86 100644
--- a/spec/fixtures/api/schemas/user/login.json
+++ b/spec/fixtures/api/schemas/user/login.json
@@ -19,7 +19,6 @@
"organization",
"last_sign_in_at",
"confirmed_at",
- "theme_id",
"color_scheme_id",
"projects_limit",
"current_sign_in_at",
diff --git a/spec/fixtures/api/schemas/user/public.json b/spec/fixtures/api/schemas/user/public.json
index dbd5d32e89c..5587cfec61a 100644
--- a/spec/fixtures/api/schemas/user/public.json
+++ b/spec/fixtures/api/schemas/user/public.json
@@ -19,7 +19,6 @@
"organization",
"last_sign_in_at",
"confirmed_at",
- "theme_id",
"color_scheme_id",
"projects_limit",
"current_sign_in_at",
@@ -32,14 +31,14 @@
"properties": {
"id": { "type": "integer" },
"username": { "type": "string" },
- "email": {
+ "email": {
"type": "string",
"pattern": "^[^@]+@[^@]+$"
},
"name": { "type": "string" },
- "state": {
+ "state": {
"type": "string",
- "enum": ["active", "blocked"]
+ "enum": ["active", "blocked"]
},
"avatar_url": { "type": "string" },
"web_url": { "type": "string" },
@@ -54,18 +53,17 @@
"organization": { "type": ["string", "null"] },
"last_sign_in_at": { "type": "date" },
"confirmed_at": { "type": ["date", "null"] },
- "theme_id": { "type": "integer" },
"color_scheme_id": { "type": "integer" },
"projects_limit": { "type": "integer" },
"current_sign_in_at": { "type": "date" },
- "identities": {
+ "identities": {
"type": "array",
"items": {
"type": "object",
"properties": {
- "provider": {
+ "provider": {
"type": "string",
- "enum": ["github", "bitbucket", "google_oauth2"]
+ "enum": ["github", "bitbucket", "google_oauth2"]
},
"extern_uid": { "type": ["number", "string"] }
}
@@ -74,6 +72,6 @@
"can_create_group": { "type": "boolean" },
"can_create_project": { "type": "boolean" },
"two_factor_enabled": { "type": "boolean" },
- "external": { "type": "boolean" }
+ "external": { "type": "boolean" }
}
}
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 8b201f348f1..fd40fe99941 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -265,4 +265,9 @@ describe ApplicationHelper do
expect(helper.render_markup('foo.adoc', content)).to eq('NOEL')
end
end
+
+ describe '#active_when' do
+ it { expect(helper.active_when(true)).to eq('active') }
+ it { expect(helper.active_when(false)).to eq(nil) }
+ end
end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index dc07657e101..2cc0b40b2d0 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -40,6 +40,18 @@ describe PageLayoutHelper do
end
end
+ describe 'favicon' do
+ it 'defaults to favicon.ico' do
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
+ expect(helper.favicon).to eq 'favicon.ico'
+ end
+
+ it 'has blue favicon for development' do
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
+ expect(helper.favicon).to eq 'favicon-blue.ico'
+ end
+ end
+
describe 'page_image' do
it 'defaults to the GitLab logo' do
expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 1f02e06e312..f3e79cc7290 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -26,32 +26,6 @@ describe PreferencesHelper do
end
end
- describe 'user_application_theme' do
- context 'with a user' do
- it "returns user's theme's css_class" do
- stub_user(theme_id: 3)
-
- expect(helper.user_application_theme).to eq 'ui_green'
- end
-
- it 'returns the default when id is invalid' do
- stub_user(theme_id: Gitlab::Themes.count + 5)
-
- allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
-
- expect(helper.user_application_theme).to eq 'ui_charcoal'
- end
- end
-
- context 'without a user' do
- it 'returns the default theme' do
- stub_user
-
- expect(helper.user_application_theme).to eq Gitlab::Themes.default.css_class
- end
- end
- end
-
describe 'user_color_scheme' do
context 'with a user' do
it "returns user's scheme's css_class" do
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
new file mode 100644
index 00000000000..92c6f27a867
--- /dev/null
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe WikiHelper do
+ describe '#breadcrumb' do
+ context 'when the page is at the root level' do
+ it 'returns the capitalized page name' do
+ slug = 'page-name'
+
+ expect(helper.breadcrumb(slug)).to eq('Page name')
+ end
+ end
+
+ context 'when the page is inside a directory' do
+ it 'returns the capitalized name of each directory and of the page itself' do
+ slug = 'dir_1/page-name'
+
+ expect(helper.breadcrumb(slug)).to eq('Dir_1 / Page name')
+ end
+ end
+ end
+end
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index 1541037888f..0e4c2c560cc 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -5,73 +5,83 @@ require('~/behaviors/quick_submit');
(function() {
describe('Quick Submit behavior', function() {
var keydownEvent;
- preloadFixtures('static/behaviors/quick_submit.html.raw');
+ preloadFixtures('issues/open-issue.html.raw');
beforeEach(function() {
- loadFixtures('static/behaviors/quick_submit.html.raw');
+ loadFixtures('issues/open-issue.html.raw');
$('form').submit(function(e) {
// Prevent a form submit from moving us off the testing page
return e.preventDefault();
});
- return this.spies = {
+ this.spies = {
submit: spyOnEvent('form', 'submit')
};
+
+ this.textarea = $('.js-quick-submit textarea').first();
});
it('does not respond to other keyCodes', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
keyCode: 32
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
it('does not respond to Enter alone', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
ctrlKey: false,
metaKey: false
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
it('does not respond to repeated events', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
repeat: true
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
- it('disables submit buttons', function() {
- $('textarea').trigger(keydownEvent());
- expect($('input[type=submit]')).toBeDisabled();
- return expect($('button[type=submit]')).toBeDisabled();
+ it('disables input of type submit', function() {
+ const submitButton = $('.js-quick-submit input[type=submit]');
+ this.textarea.trigger(keydownEvent());
+ expect(submitButton).toBeDisabled();
+ });
+ it('disables button of type submit', function() {
+ // button doesn't exist in fixture, add it manually
+ const submitButton = $('<button type="submit">Submit it</button>');
+ submitButton.insertAfter(this.textarea);
+
+ this.textarea.trigger(keydownEvent());
+ expect(submitButton).toBeDisabled();
});
// We cannot stub `navigator.userAgent` for CI's `rake karma` task, so we'll
// only run the tests that apply to the current platform
if (navigator.userAgent.match(/Macintosh/)) {
it('responds to Meta+Enter', function() {
- $('input.quick-submit-input').trigger(keydownEvent());
+ this.textarea.trigger(keydownEvent());
return expect(this.spies.submit).toHaveBeenTriggered();
});
it('excludes other modifier keys', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
altKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
ctrlKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
shiftKey: true
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
} else {
it('responds to Ctrl+Enter', function() {
- $('input.quick-submit-input').trigger(keydownEvent());
+ this.textarea.trigger(keydownEvent());
return expect(this.spies.submit).toHaveBeenTriggered();
});
it('excludes other modifier keys', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
altKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
metaKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
shiftKey: true
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index a958ac76e66..631fca06514 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -4,18 +4,19 @@ require('~/behaviors/requires_input');
(function() {
describe('requiresInput', function() {
- preloadFixtures('static/behaviors/requires_input.html.raw');
+ preloadFixtures('branches/new_branch.html.raw');
beforeEach(function() {
- return loadFixtures('static/behaviors/requires_input.html.raw');
+ loadFixtures('branches/new_branch.html.raw');
+ this.submitButton = $('button[type="submit"]');
});
it('disables submit when any field is required', function() {
$('.js-requires-input').requiresInput();
- return expect($('.submit')).toBeDisabled();
+ return expect(this.submitButton).toBeDisabled();
});
it('enables submit when no field is required', function() {
$('*[required=required]').removeAttr('required');
$('.js-requires-input').requiresInput();
- return expect($('.submit')).not.toBeDisabled();
+ return expect(this.submitButton).not.toBeDisabled();
});
it('enables submit when all required fields are pre-filled', function() {
$('*[required=required]').remove();
@@ -25,9 +26,9 @@ require('~/behaviors/requires_input');
it('enables submit when all required fields receive input', function() {
$('.js-requires-input').requiresInput();
$('#required1').val('input1').change();
- expect($('.submit')).toBeDisabled();
+ expect(this.submitButton).toBeDisabled();
$('#optional1').val('input1').change();
- expect($('.submit')).toBeDisabled();
+ expect(this.submitButton).toBeDisabled();
$('#required2').val('input2').change();
$('#required3').val('input3').change();
$('#required4').val('input4').change();
diff --git a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
index 789f5dc9f49..94973419979 100644
--- a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
+++ b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
@@ -1,10 +1,10 @@
-require('~/commit/pipelines/pipelines_store');
+const PipelinesStore = require('~/commit/pipelines/pipelines_store');
describe('Store', () => {
let store;
beforeEach(() => {
- store = new gl.commits.pipelines.PipelinesStore();
+ store = new PipelinesStore();
});
// unregister intervals and event handlers
diff --git a/spec/javascripts/dashboard_spec.js.es6 b/spec/javascripts/dashboard_spec.js.es6
deleted file mode 100644
index c0bdb89ed63..00000000000
--- a/spec/javascripts/dashboard_spec.js.es6
+++ /dev/null
@@ -1,37 +0,0 @@
-/* eslint-disable no-new */
-
-require('~/sidebar');
-require('~/lib/utils/text_utility');
-
-((global) => {
- describe('Dashboard', () => {
- const fixtureTemplate = 'static/dashboard.html.raw';
-
- function todosCountText() {
- return $('.js-todos-count').text();
- }
-
- function triggerToggle(newCount) {
- $(document).trigger('todo:toggle', newCount);
- }
-
- preloadFixtures(fixtureTemplate);
- beforeEach(() => {
- loadFixtures(fixtureTemplate);
- new global.Sidebar();
- });
-
- it('should update todos-count after receiving the todo:toggle event', () => {
- triggerToggle(5);
- expect(todosCountText()).toEqual('5');
- });
-
- it('should display todos-count with delimiter', () => {
- triggerToggle(1000);
- expect(todosCountText()).toEqual('1,000');
-
- triggerToggle(1000000);
- expect(todosCountText()).toEqual('1,000,000');
- });
- });
-})(window.gl);
diff --git a/spec/javascripts/environments/environment_actions_spec.js.es6 b/spec/javascripts/environments/environment_actions_spec.js.es6
index b1838045a06..850586f9f3a 100644
--- a/spec/javascripts/environments/environment_actions_spec.js.es6
+++ b/spec/javascripts/environments/environment_actions_spec.js.es6
@@ -1,4 +1,4 @@
-require('~/environments/components/environment_actions');
+const ActionsComponent = require('~/environments/components/environment_actions');
describe('Actions Component', () => {
preloadFixtures('static/environments/element.html.raw');
@@ -19,7 +19,7 @@ describe('Actions Component', () => {
},
];
- const component = new window.gl.environmentsList.ActionsComponent({
+ const component = new ActionsComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
actions: actionsMock,
@@ -47,7 +47,7 @@ describe('Actions Component', () => {
},
];
- const component = new window.gl.environmentsList.ActionsComponent({
+ const component = new ActionsComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
actions: actionsMock,
diff --git a/spec/javascripts/environments/environment_external_url_spec.js.es6 b/spec/javascripts/environments/environment_external_url_spec.js.es6
index a6a587e69f5..393dbb5aae0 100644
--- a/spec/javascripts/environments/environment_external_url_spec.js.es6
+++ b/spec/javascripts/environments/environment_external_url_spec.js.es6
@@ -1,4 +1,4 @@
-require('~/environments/components/environment_external_url');
+const ExternalUrlComponent = require('~/environments/components/environment_external_url');
describe('External URL Component', () => {
preloadFixtures('static/environments/element.html.raw');
@@ -8,7 +8,7 @@ describe('External URL Component', () => {
it('should link to the provided externalUrl prop', () => {
const externalURL = 'https://gitlab.com';
- const component = new window.gl.environmentsList.ExternalUrlComponent({
+ const component = new ExternalUrlComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
externalUrl: externalURL,
diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6
index e6fb0d00290..7fea80ed799 100644
--- a/spec/javascripts/environments/environment_item_spec.js.es6
+++ b/spec/javascripts/environments/environment_item_spec.js.es6
@@ -1,5 +1,5 @@
window.timeago = require('timeago.js');
-require('~/environments/components/environment_item');
+const EnvironmentItem = require('~/environments/components/environment_item');
describe('Environment item', () => {
preloadFixtures('static/environments/table.html.raw');
@@ -14,33 +14,16 @@ describe('Environment item', () => {
beforeEach(() => {
mockItem = {
name: 'review',
- children: [
- {
- name: 'review-app',
- id: 1,
- state: 'available',
- external_url: '',
- last_deployment: {},
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-10T15:55:58.778Z',
- },
- {
- name: 'production',
- id: 2,
- state: 'available',
- external_url: '',
- last_deployment: {},
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-10T15:55:58.778Z',
- },
- ],
+ folderName: 'review',
+ size: 3,
+ isFolder: true,
+ environment_path: 'url',
};
- component = new window.gl.environmentsList.EnvironmentItem({
+ component = new EnvironmentItem({
el: document.querySelector('tr#environment-row'),
propsData: {
model: mockItem,
- toggleRow: () => {},
canCreateDeployment: false,
canReadEnvironment: true,
},
@@ -53,7 +36,7 @@ describe('Environment item', () => {
});
it('Should render the number of children in a badge', () => {
- expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(mockItem.children.length);
+ expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(mockItem.size);
});
});
@@ -63,8 +46,8 @@ describe('Environment item', () => {
beforeEach(() => {
environment = {
- id: 31,
name: 'production',
+ size: 1,
state: 'stopped',
external_url: 'http://external.com',
environment_type: null,
@@ -125,11 +108,10 @@ describe('Environment item', () => {
updated_at: '2016-11-10T15:55:58.778Z',
};
- component = new window.gl.environmentsList.EnvironmentItem({
+ component = new EnvironmentItem({
el: document.querySelector('tr#environment-row'),
propsData: {
model: environment,
- toggleRow: () => {},
canCreateDeployment: true,
canReadEnvironment: true,
},
diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js.es6
index 043b8708a6e..4a596baad09 100644
--- a/spec/javascripts/environments/environment_rollback_spec.js.es6
+++ b/spec/javascripts/environments/environment_rollback_spec.js.es6
@@ -1,4 +1,4 @@
-require('~/environments/components/environment_rollback');
+const RollbackComponent = require('~/environments/components/environment_rollback');
describe('Rollback Component', () => {
preloadFixtures('static/environments/element.html.raw');
@@ -10,7 +10,7 @@ describe('Rollback Component', () => {
});
it('Should link to the provided retryUrl', () => {
- const component = new window.gl.environmentsList.RollbackComponent({
+ const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
@@ -22,7 +22,7 @@ describe('Rollback Component', () => {
});
it('Should render Re-deploy label when isLastDeployment is true', () => {
- const component = new window.gl.environmentsList.RollbackComponent({
+ const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
@@ -34,7 +34,7 @@ describe('Rollback Component', () => {
});
it('Should render Rollback label when isLastDeployment is false', () => {
- const component = new window.gl.environmentsList.RollbackComponent({
+ const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6
index 87eda136122..edd0cad32d0 100644
--- a/spec/javascripts/environments/environment_spec.js.es6
+++ b/spec/javascripts/environments/environment_spec.js.es6
@@ -1,9 +1,7 @@
-/* global Vue, environment */
-
+const Vue = require('vue');
require('~/flash');
-require('~/environments/stores/environments_store');
-require('~/environments/components/environment');
-require('./mock_data');
+const EnvironmentsComponent = require('~/environments/components/environment');
+const { environment } = require('./mock_data');
describe('Environment', () => {
preloadFixtures('static/environments/environments.html.raw');
@@ -33,11 +31,8 @@ describe('Environment', () => {
});
it('should render the empty state', (done) => {
- component = new gl.environmentsList.EnvironmentsComponent({
+ component = new EnvironmentsComponent({
el: document.querySelector('#environments-list-view'),
- propsData: {
- store: gl.environmentsList.EnvironmentsStore.create(),
- },
});
setTimeout(() => {
@@ -54,15 +49,30 @@ describe('Environment', () => {
});
});
- describe('with environments', () => {
+ describe('with paginated environments', () => {
const environmentsResponseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([environment]), {
+ next(request.respondWith(JSON.stringify({
+ environments: [environment],
+ stopped_count: 1,
+ available_count: 0,
+ }), {
status: 200,
+ headers: {
+ 'X-nExt-pAge': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ },
}));
};
beforeEach(() => {
Vue.http.interceptors.push(environmentsResponseInterceptor);
+ component = new EnvironmentsComponent({
+ el: document.querySelector('#environments-list-view'),
+ });
});
afterEach(() => {
@@ -72,13 +82,6 @@ describe('Environment', () => {
});
it('should render a table with environments', (done) => {
- component = new gl.environmentsList.EnvironmentsComponent({
- el: document.querySelector('#environments-list-view'),
- propsData: {
- store: gl.environmentsList.EnvironmentsStore.create(),
- },
- });
-
setTimeout(() => {
expect(
component.$el.querySelectorAll('table tbody tr').length,
@@ -86,6 +89,59 @@ describe('Environment', () => {
done();
}, 0);
});
+
+ describe('pagination', () => {
+ it('should render pagination', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelectorAll('.gl-pagination li').length,
+ ).toEqual(5);
+ done();
+ }, 0);
+ });
+
+ it('should update url when no search params are present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page is already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?scope=all&page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present and page is first param', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1&scope=all');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all');
+ done();
+ }, 0);
+ });
+ });
});
});
@@ -107,11 +163,8 @@ describe('Environment', () => {
});
it('should render empty state', (done) => {
- component = new gl.environmentsList.EnvironmentsComponent({
+ component = new EnvironmentsComponent({
el: document.querySelector('#environments-list-view'),
- propsData: {
- store: gl.environmentsList.EnvironmentsStore.create(),
- },
});
setTimeout(() => {
diff --git a/spec/javascripts/environments/environment_stop_spec.js.es6 b/spec/javascripts/environments/environment_stop_spec.js.es6
index 2dfce5ba824..5ca65b1debc 100644
--- a/spec/javascripts/environments/environment_stop_spec.js.es6
+++ b/spec/javascripts/environments/environment_stop_spec.js.es6
@@ -1,4 +1,4 @@
-require('~/environments/components/environment_stop');
+const StopComponent = require('~/environments/components/environment_stop');
describe('Stop Component', () => {
preloadFixtures('static/environments/element.html.raw');
@@ -10,7 +10,7 @@ describe('Stop Component', () => {
loadFixtures('static/environments/element.html.raw');
stopURL = '/stop';
- component = new window.gl.environmentsList.StopComponent({
+ component = new StopComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
stopUrl: stopURL,
diff --git a/spec/javascripts/environments/environment_table_spec.js.es6 b/spec/javascripts/environments/environment_table_spec.js.es6
new file mode 100644
index 00000000000..be4330b5012
--- /dev/null
+++ b/spec/javascripts/environments/environment_table_spec.js.es6
@@ -0,0 +1,30 @@
+const EnvironmentTable = require('~/environments/components/environments_table');
+
+describe('Environment item', () => {
+ preloadFixtures('static/environments/element.html.raw');
+ beforeEach(() => {
+ loadFixtures('static/environments/element.html.raw');
+ });
+
+ it('Should render a table', () => {
+ const mockItem = {
+ name: 'review',
+ size: 3,
+ isFolder: true,
+ latest: {
+ environment_path: 'url',
+ },
+ };
+
+ const component = new EnvironmentTable({
+ el: document.querySelector('.test-dom-element'),
+ propsData: {
+ environments: [{ mockItem }],
+ canCreateDeployment: false,
+ canReadEnvironment: true,
+ },
+ });
+
+ expect(component.$el.tagName).toEqual('TABLE');
+ });
+});
diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6
index 9a8300d3832..77e182b3830 100644
--- a/spec/javascripts/environments/environments_store_spec.js.es6
+++ b/spec/javascripts/environments/environments_store_spec.js.es6
@@ -1,70 +1,58 @@
-/* global environmentsList */
-
-require('~/environments/stores/environments_store');
-require('./mock_data');
+const Store = require('~/environments/stores/environments_store');
+const { environmentsList, serverData } = require('./mock_data');
(() => {
describe('Store', () => {
+ let store;
+
beforeEach(() => {
- gl.environmentsList.EnvironmentsStore.create();
+ store = new Store();
});
it('should start with a blank state', () => {
- expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(0);
- expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(0);
- expect(gl.environmentsList.EnvironmentsStore.state.availableCounter).toBe(0);
+ expect(store.state.environments.length).toEqual(0);
+ expect(store.state.stoppedCounter).toEqual(0);
+ expect(store.state.availableCounter).toEqual(0);
+ expect(store.state.paginationInformation).toEqual({});
});
- describe('store environments', () => {
- beforeEach(() => {
- gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList);
- });
-
- it('should count stopped environments and save the count in the state', () => {
- expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(1);
- });
-
- it('should count available environments and save the count in the state', () => {
- expect(gl.environmentsList.EnvironmentsStore.state.availableCounter).toBe(3);
- });
-
- it('should store environments with same environment_type as sibilings', () => {
- expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(3);
-
- const parentFolder = gl.environmentsList.EnvironmentsStore.state.environments
- .filter(env => env.children && env.children.length > 0);
-
- expect(parentFolder[0].children.length).toBe(2);
- expect(parentFolder[0].children[0].environment_type).toBe('review');
- expect(parentFolder[0].children[1].environment_type).toBe('review');
- expect(parentFolder[0].children[0].name).toBe('test-environment');
- expect(parentFolder[0].children[1].name).toBe('test-environment-1');
- });
-
- it('should sort the environments alphabetically', () => {
- const { environments } = gl.environmentsList.EnvironmentsStore.state;
-
- expect(environments[0].name).toBe('production');
- expect(environments[1].name).toBe('review');
- expect(environments[1].children[0].name).toBe('test-environment');
- expect(environments[1].children[1].name).toBe('test-environment-1');
- expect(environments[2].name).toBe('review_app');
- });
+ it('should store environments', () => {
+ store.storeEnvironments(serverData);
+ expect(store.state.environments.length).toEqual(serverData.length);
+ expect(store.state.environments[0]).toEqual(environmentsList[0]);
});
- describe('toggleFolder', () => {
- beforeEach(() => {
- gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList);
- });
-
- it('should toggle the open property for the given environment', () => {
- gl.environmentsList.EnvironmentsStore.toggleFolder('review');
+ it('should store available count', () => {
+ store.storeAvailableCount(2);
+ expect(store.state.availableCounter).toEqual(2);
+ });
- const { environments } = gl.environmentsList.EnvironmentsStore.state;
- const environment = environments.filter(env => env['vue-isChildren'] === true && env.name === 'review');
+ it('should store stopped count', () => {
+ store.storeStoppedCount(2);
+ expect(store.state.stoppedCounter).toEqual(2);
+ });
- expect(environment[0].isOpen).toBe(true);
- });
+ it('should store pagination information', () => {
+ const pagination = {
+ 'X-nExt-pAge': '2',
+ 'X-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '2',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ };
+
+ const expectedResult = {
+ perPage: 1,
+ page: 1,
+ total: 37,
+ totalPages: 2,
+ nextPage: 2,
+ previousPage: 2,
+ };
+
+ store.setPagination(pagination);
+ expect(store.state.paginationInformation).toEqual(expectedResult);
});
});
})();
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6 b/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6
new file mode 100644
index 00000000000..d1335b5b304
--- /dev/null
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6
@@ -0,0 +1,202 @@
+const Vue = require('vue');
+require('~/flash');
+const EnvironmentsFolderViewComponent = require('~/environments/folder/environments_folder_view');
+const { environmentsList } = require('../mock_data');
+
+describe('Environments Folder View', () => {
+ preloadFixtures('static/environments/environments_folder_view.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('static/environments/environments_folder_view.html.raw');
+ window.history.pushState({}, null, 'environments/folders/build');
+ });
+
+ let component;
+
+ describe('successfull request', () => {
+ const environmentsResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({
+ environments: environmentsList,
+ stopped_count: 1,
+ available_count: 0,
+ }), {
+ status: 200,
+ headers: {
+ 'X-nExt-pAge': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ },
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsResponseInterceptor);
+ component = new EnvironmentsFolderViewComponent({
+ el: document.querySelector('#environments-folder-list-view'),
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsResponseInterceptor,
+ );
+ });
+
+ it('should render a table with environments', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelectorAll('table tbody tr').length,
+ ).toEqual(2);
+ done();
+ }, 0);
+ });
+
+ it('should render available tab with count', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab').textContent,
+ ).toContain('Available');
+
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
+ ).toContain('0');
+ done();
+ }, 0);
+ });
+
+ it('should render stopped tab with count', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
+ ).toContain('Stopped');
+
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
+ ).toContain('1');
+ done();
+ }, 0);
+ });
+
+ it('should render parent folder name', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-folder-name').textContent,
+ ).toContain('Environments / build');
+ done();
+ }, 0);
+ });
+
+ describe('pagination', () => {
+ it('should render pagination', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelectorAll('.gl-pagination li').length,
+ ).toEqual(5);
+ done();
+ }, 0);
+ });
+
+ it('should update url when no search params are present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page is already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?scope=all&page=1');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2');
+ done();
+ }, 0);
+ });
+
+ it('should update url when page and scope are already present and page is first param', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ window.history.pushState({}, null, '?page=1&scope=all');
+
+ setTimeout(() => {
+ component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all');
+ done();
+ }, 0);
+ });
+ });
+ });
+
+ describe('unsuccessfull request', () => {
+ const environmentsErrorResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify([]), {
+ status: 500,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsErrorResponseInterceptor);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsErrorResponseInterceptor,
+ );
+ });
+
+ it('should not render a table', (done) => {
+ component = new EnvironmentsFolderViewComponent({
+ el: document.querySelector('#environments-folder-list-view'),
+ });
+
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('table'),
+ ).toBe(null);
+ done();
+ }, 0);
+ });
+
+ it('should render available tab with count 0', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab').textContent,
+ ).toContain('Available');
+
+ expect(
+ component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
+ ).toContain('0');
+ done();
+ }, 0);
+ });
+
+ it('should render stopped tab with count 0', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
+ ).toContain('Stopped');
+
+ expect(
+ component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
+ ).toContain('0');
+ done();
+ }, 0);
+ });
+ });
+});
diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6
index 80e1cbc6f4d..5c395c6b2d8 100644
--- a/spec/javascripts/environments/mock_data.js.es6
+++ b/spec/javascripts/environments/mock_data.js.es6
@@ -1,153 +1,92 @@
-
const environmentsList = [
{
- id: 31,
- name: 'production',
+ name: 'DEV',
+ size: 1,
+ id: 7,
state: 'available',
- external_url: 'https://www.gitlab.com',
- environment_type: null,
- last_deployment: {
- id: 64,
- iid: 5,
- sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- ref: {
- name: 'master',
- ref_url: 'http://localhost:3000/root/ci-folders/tree/master',
- },
- tag: false,
- 'last?': true,
- user: {
- name: 'Administrator',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- commit: {
- id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- short_id: '500aabcb',
- title: 'Update .gitlab-ci.yml',
- author_name: 'Administrator',
- author_email: 'admin@example.com',
- created_at: '2016-11-07T18:28:13.000+00:00',
- message: 'Update .gitlab-ci.yml',
- author: {
- name: 'Administrator',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- },
- deployable: {
- id: 1278,
- name: 'build',
- build_path: '/root/ci-folders/builds/1278',
- retry_path: '/root/ci-folders/builds/1278/retry',
- },
- manual_actions: [],
- },
- 'stop_action?': true,
- environment_path: '/root/ci-folders/environments/31',
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-07T11:11:16.525Z',
- },
- {
- id: 32,
- name: 'review_app',
- state: 'stopped',
- external_url: 'https://www.gitlab.com',
+ external_url: null,
environment_type: null,
- last_deployment: {
- id: 64,
- iid: 5,
- sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- ref: {
- name: 'master',
- ref_url: 'http://localhost:3000/root/ci-folders/tree/master',
- },
- tag: false,
- 'last?': true,
- user: {
- name: 'Administrator',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- commit: {
- id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- short_id: '500aabcb',
- title: 'Update .gitlab-ci.yml',
- author_name: 'Administrator',
- author_email: 'admin@example.com',
- created_at: '2016-11-07T18:28:13.000+00:00',
- message: 'Update .gitlab-ci.yml',
- author: {
- name: 'Administrator',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
- },
- deployable: {
- id: 1278,
- name: 'build',
- build_path: '/root/ci-folders/builds/1278',
- retry_path: '/root/ci-folders/builds/1278/retry',
- },
- manual_actions: [],
- },
+ last_deployment: null,
'stop_action?': false,
- environment_path: '/root/ci-folders/environments/31',
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-07T11:11:16.525Z',
+ environment_path: '/root/review-app/environments/7',
+ stop_path: '/root/review-app/environments/7/stop',
+ created_at: '2017-01-31T10:53:46.894Z',
+ updated_at: '2017-01-31T10:53:46.894Z',
},
{
- id: 33,
- name: 'test-environment',
+ folderName: 'build',
+ size: 5,
+ id: 12,
+ name: 'build/update-README',
state: 'available',
- environment_type: 'review',
+ external_url: null,
+ environment_type: 'build',
last_deployment: null,
- 'stop_action?': true,
- environment_path: '/root/ci-folders/environments/31',
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-07T11:11:16.525Z',
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/12',
+ stop_path: '/root/review-app/environments/12/stop',
+ created_at: '2017-02-01T19:42:18.400Z',
+ updated_at: '2017-02-01T19:42:18.400Z',
},
+];
+
+const serverData = [
{
- id: 34,
- name: 'test-environment-1',
- state: 'available',
- environment_type: 'review',
- last_deployment: null,
- 'stop_action?': true,
- environment_path: '/root/ci-folders/environments/31',
- created_at: '2016-11-07T11:11:16.525Z',
- updated_at: '2016-11-07T11:11:16.525Z',
+ name: 'DEV',
+ size: 1,
+ latest: {
+ id: 7,
+ name: 'DEV',
+ state: 'available',
+ external_url: null,
+ environment_type: null,
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/7',
+ stop_path: '/root/review-app/environments/7/stop',
+ created_at: '2017-01-31T10:53:46.894Z',
+ updated_at: '2017-01-31T10:53:46.894Z',
+ },
+ },
+ {
+ name: 'build',
+ size: 5,
+ latest: {
+ id: 12,
+ name: 'build/update-README',
+ state: 'available',
+ external_url: null,
+ environment_type: 'build',
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/12',
+ stop_path: '/root/review-app/environments/12/stop',
+ created_at: '2017-02-01T19:42:18.400Z',
+ updated_at: '2017-02-01T19:42:18.400Z',
+ },
},
];
-window.environmentsList = environmentsList;
-
const environment = {
- id: 4,
- name: 'production',
- state: 'available',
- external_url: 'http://production.',
- environment_type: null,
- last_deployment: {},
- 'stop_action?': false,
- environment_path: '/root/review-app/environments/4',
- stop_path: '/root/review-app/environments/4/stop',
- created_at: '2016-12-16T11:51:04.690Z',
- updated_at: '2016-12-16T12:04:51.133Z',
+ name: 'DEV',
+ size: 1,
+ latest: {
+ id: 7,
+ name: 'DEV',
+ state: 'available',
+ external_url: null,
+ environment_type: null,
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/7',
+ stop_path: '/root/review-app/environments/7/stop',
+ created_at: '2017-01-31T10:53:46.894Z',
+ updated_at: '2017-01-31T10:53:46.894Z',
+ },
};
-window.environment = environment;
+module.exports = {
+ environmentsList,
+ environment,
+ serverData,
+};
diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore
index 009b68d5d1c..0c35cdd778e 100644
--- a/spec/javascripts/fixtures/.gitignore
+++ b/spec/javascripts/fixtures/.gitignore
@@ -1 +1,2 @@
*.html.raw
+*.json
diff --git a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
deleted file mode 100644
index dc2ceed42f4..00000000000
--- a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-%form.js-quick-submit{ action: '/foo' }
- %input{ type: 'text', class: 'quick-submit-input'}
- %textarea
-
- %input{ type: 'submit'} Submit
- %button.btn{ type: 'submit' } Submit
diff --git a/spec/javascripts/fixtures/behaviors/requires_input.html.haml b/spec/javascripts/fixtures/behaviors/requires_input.html.haml
deleted file mode 100644
index c3f905e912e..00000000000
--- a/spec/javascripts/fixtures/behaviors/requires_input.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-%form.js-requires-input
- %input{type: 'text', id: 'required1', required: 'required'}
- %input{type: 'text', id: 'required2', required: 'required'}
- %input{type: 'text', id: 'required3', required: 'required', value: 'Pre-filled'}
- %input{type: 'text', id: 'optional1'}
-
- %textarea{id: 'required4', required: 'required'}
- %textarea{id: 'optional2'}
-
- %select{id: 'required5', required: 'required'}
- %option Zero
- %option{value: '1'} One
- %select{id: 'optional3', required: 'required'}
- %option Zero
- %option{value: '1'} One
-
- %button.submit{type: 'submit', value: 'Submit'}
- %input.submit{type: 'submit', value: 'Submit'}
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
new file mode 100644
index 00000000000..0e7c2351b66
--- /dev/null
+++ b/spec/javascripts/fixtures/branches.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('branches/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'branches/new_branch.html.raw' do |example|
+ get :new,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/dashboard.html.haml b/spec/javascripts/fixtures/dashboard.html.haml
deleted file mode 100644
index 32446acfd60..00000000000
--- a/spec/javascripts/fixtures/dashboard.html.haml
+++ /dev/null
@@ -1,45 +0,0 @@
-%ul.nav.nav-sidebar
- %li.home.active
- %a.dashboard-shortcuts-projects
- %span
- Projects
- %li
- %a
- %span
- Todos
- %span.count.js-todos-count
- 1
- %li
- %a.dashboard-shortcuts-activity
- %span
- Activity
- %li
- %a
- %span
- Groups
- %li
- %a
- %span
- Milestones
- %li
- %a.dashboard-shortcuts-issues
- %span
- Issues
- %span
- 1
- %li
- %a.dashboard-shortcuts-merge_requests
- %span
- Merge Requests
- %li
- %a
- %span
- Snippets
- %li
- %a
- %span
- Help
- %li
- %a
- %span
- Profile Settings
diff --git a/spec/javascripts/fixtures/environments/environments_folder_view.html.haml b/spec/javascripts/fixtures/environments/environments_folder_view.html.haml
new file mode 100644
index 00000000000..aceec139730
--- /dev/null
+++ b/spec/javascripts/fixtures/environments/environments_folder_view.html.haml
@@ -0,0 +1,7 @@
+%div
+ #environments-folder-list-view{ data: { "can-create-deployment" => "true",
+ "can-read-environment" => "true",
+ "css-class" => "",
+ "commit-icon-svg" => custom_icon("icon_commit"),
+ "terminal-icon-svg" => custom_icon("icon_terminal"),
+ "play-icon-svg" => custom_icon("icon_play") } }
diff --git a/spec/javascripts/fixtures/header.html.haml b/spec/javascripts/fixtures/header.html.haml
deleted file mode 100644
index f397f69e753..00000000000
--- a/spec/javascripts/fixtures/header.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-%header.navbar.navbar-gitlab.nav_header_class
- .container-fluid
- .header-content
- %button.side-nav-toggle
- %span.sr-only
- Toggle navigation
- %i.fa.fa-bars
- %button.navbar-toggle
- %span.sr-only
- Toggle navigation
- %i.fa.fa-ellipsis-v
- .navbar-collapse.collapse
- %ui.nav.navbar-nav
- %li.hidden-sm.hidden-xs
- %li.visible-sm.visible-xs
- %li
- %a
- %i.fa.fa-bell.fa-fw
- %span.badge.todos-pending-count
- %li
- %a
- %i.fa.fa-plus.fa-fw
- %li.header-user.dropdown
- %a
- %img
- %span.caret
- .dropdown-menu-nav
- .dropdown-menu-align-right
- %ul
- %li
- %a.profile-link
- %li
- %a
- %li.divider
- %li.sign-out-link
diff --git a/spec/javascripts/fixtures/merge_request_tabs.html.haml b/spec/javascripts/fixtures/merge_request_tabs.html.haml
deleted file mode 100644
index 68678c3d7e3..00000000000
--- a/spec/javascripts/fixtures/merge_request_tabs.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-%ul.nav.nav-tabs.merge-request-tabs
- %li.notes-tab
- %a{href: '/foo/bar/merge_requests/1', data: {target: 'div#notes', action: 'notes', toggle: 'tab'}}
- Discussion
- %li.commits-tab
- %a{href: '/foo/bar/merge_requests/1/commits', data: {target: 'div#commits', action: 'commits', toggle: 'tab'}}
- Commits
- %li.diffs-tab
- %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'}}
- Diffs
-
-.tab-content
- #notes.notes.tab-pane
- Notes Content
- #commits.commits.tab-pane
- Commits Content
- #diffs.diffs.tab-pane
- Diffs Content
-
-.mr-loading-status
- .loading
- Loading Animation
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
new file mode 100644
index 00000000000..62984097099
--- /dev/null
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('merge_requests/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'merge_requests/merge_request_with_task_list.html.raw' do |example|
+ merge_request = create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item')
+ render_merge_request(example.description, merge_request)
+ end
+
+ private
+
+ def render_merge_request(fixture_file_name, merge_request)
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.to_param
+
+ expect(response).to be_success
+ store_frontend_fixture(response, fixture_file_name)
+ end
+end
diff --git a/spec/javascripts/fixtures/new_branch.html.haml b/spec/javascripts/fixtures/new_branch.html.haml
deleted file mode 100644
index f06629e5ecc..00000000000
--- a/spec/javascripts/fixtures/new_branch.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%form.js-create-branch-form
- %input.js-branch-name
- .js-branch-name-error
- %input{id: "ref"}
diff --git a/spec/javascripts/fixtures/todos.json b/spec/javascripts/fixtures/todos.json
deleted file mode 100644
index 62c2387d515..00000000000
--- a/spec/javascripts/fixtures/todos.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "count": 1,
- "delete_path": "/dashboard/todos/1"
-} \ No newline at end of file
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb
new file mode 100644
index 00000000000..2c08b06ea9e
--- /dev/null
+++ b/spec/javascripts/fixtures/todos.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe 'Todos (JavaScript fixtures)' do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
+ let(:issue_1) { create(:issue, title: 'issue_1', project: project) }
+ let!(:todo_1) { create(:todo, user: admin, project: project, target: issue_1, created_at: 5.hours.ago) }
+ let(:issue_2) { create(:issue, title: 'issue_2', project: project) }
+ let!(:todo_2) { create(:todo, :done, user: admin, project: project, target: issue_2, created_at: 50.hours.ago) }
+
+ before(:all) do
+ clean_frontend_fixtures('todos/')
+ end
+
+ describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'todos/todos.html.raw' do |example|
+ get :index
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+ end
+
+ describe Projects::TodosController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'todos/todos.json' do |example|
+ post :create,
+ namespace_id: namespace.path,
+ project_id: project.path,
+ issuable_type: 'issue',
+ issuable_id: issue_2.id,
+ format: 'json'
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+ end
+end
diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6
index 317f38c5888..c207fb00a47 100644
--- a/spec/javascripts/gl_dropdown_spec.js.es6
+++ b/spec/javascripts/gl_dropdown_spec.js.es6
@@ -139,6 +139,14 @@ require('~/lib/utils/url_utility');
this.dropdownButtonElement.click();
});
+ it('should show loading indicator while search results are being fetched by backend', () => {
+ const dropdownMenu = document.querySelector('.dropdown-menu');
+
+ expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(true);
+ remoteCallback();
+ expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(false);
+ });
+
it('should not focus search input while remote task is not complete', () => {
expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
remoteCallback();
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index cecebb0b038..2b263b71b7d 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -6,7 +6,7 @@ require('~/lib/utils/text_utility');
(function() {
describe('Header', function() {
var todosPendingCount = '.todos-pending-count';
- var fixtureTemplate = 'static/header.html.raw';
+ var fixtureTemplate = 'issues/open-issue.html.raw';
function isTodosCountHidden() {
return $(todosPendingCount).hasClass('hidden');
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 5b0b7aa7903..beb544468ef 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -105,6 +105,7 @@ require('~/issue');
expectIssueState(false);
expect($btnClose).toHaveProp('disabled', false);
+ expect($('.issue_counter')).toHaveText(0);
});
it('fails to close an issue with success:false', function() {
@@ -121,6 +122,7 @@ require('~/issue');
expectIssueState(true);
expect($btnClose).toHaveProp('disabled', false);
expectErrorMessage();
+ expect($('.issue_counter')).toHaveText(1);
});
it('fails to closes an issue with HTTP error', function() {
@@ -135,6 +137,7 @@ require('~/issue');
expectIssueState(true);
expect($btnClose).toHaveProp('disabled', true);
expectErrorMessage();
+ expect($('.issue_counter')).toHaveText(1);
});
});
@@ -159,6 +162,7 @@ require('~/issue');
expectIssueState(true);
expect($btnReopen).toHaveProp('disabled', false);
+ expect($('.issue_counter')).toHaveText(1);
});
});
}).call(this);
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6
index 006ede21093..f4d3e77e515 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js.es6
+++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6
@@ -108,6 +108,30 @@ require('~/lib/utils/common_utils');
});
});
+ describe('gl.utils.parseIntPagination', () => {
+ it('should parse to integers all string values and return pagination object', () => {
+ const pagination = {
+ 'X-PER-PAGE': 10,
+ 'X-PAGE': 2,
+ 'X-TOTAL': 30,
+ 'X-TOTAL-PAGES': 3,
+ 'X-NEXT-PAGE': 3,
+ 'X-PREV-PAGE': 1,
+ };
+
+ const expectedPagination = {
+ perPage: 10,
+ page: 2,
+ total: 30,
+ totalPages: 3,
+ nextPage: 3,
+ previousPage: 1,
+ };
+
+ expect(gl.utils.parseIntPagination(pagination)).toEqual(expectedPagination);
+ });
+ });
+
describe('gl.utils.isMetaClick', () => {
it('should identify meta click on Windows/Linux', () => {
const e = {
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 92a0f1c05f7..5b0c124962c 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -25,7 +25,7 @@ require('vendor/jquery.scrollTo');
};
$.extend(stubLocation, defaults, stubs || {});
};
- preloadFixtures('static/merge_request_tabs.html.raw');
+ preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function () {
this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
@@ -41,7 +41,7 @@ require('vendor/jquery.scrollTo');
describe('#activateTab', function () {
beforeEach(function () {
spyOn($, 'ajax').and.callFake(function () {});
- loadFixtures('static/merge_request_tabs.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
this.subject = this.class.activateTab;
});
it('shows the first tab when action is show', function () {
@@ -62,19 +62,47 @@ require('vendor/jquery.scrollTo');
});
});
describe('#opensInNewTab', function () {
- var commitsLink;
var tabUrl;
+ var windowTarget = '_blank';
beforeEach(function () {
- commitsLink = '.commits-tab li a';
- tabUrl = $(commitsLink).attr('href');
+ loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+
+ tabUrl = $('.commits-tab a').attr('href');
spyOn($.fn, 'attr').and.returnValue(tabUrl);
});
+
+ describe('meta click', () => {
+ beforeEach(function () {
+ spyOn(gl.utils, 'isMetaClick').and.returnValue(true);
+ });
+
+ it('opens page when commits link is clicked', function () {
+ spyOn(window, 'open').and.callFake(function (url, name) {
+ expect(url).toEqual(tabUrl);
+ expect(name).toEqual(windowTarget);
+ });
+
+ this.class.bindEvents();
+ document.querySelector('.merge-request-tabs .commits-tab a').click();
+ });
+
+ it('opens page when commits badge is clicked', function () {
+ spyOn(window, 'open').and.callFake(function (url, name) {
+ expect(url).toEqual(tabUrl);
+ expect(name).toEqual(windowTarget);
+ });
+
+ this.class.bindEvents();
+ document.querySelector('.merge-request-tabs .commits-tab a .badge').click();
+ });
+ });
+
it('opens page tab in a new browser tab with Ctrl+Click - Windows/Linux', function () {
spyOn(window, 'open').and.callFake(function (url, name) {
expect(url).toEqual(tabUrl);
- expect(name).toEqual('_blank');
+ expect(name).toEqual(windowTarget);
});
this.class.clickTab({
@@ -87,7 +115,7 @@ require('vendor/jquery.scrollTo');
it('opens page tab in a new browser tab with Cmd+Click - Mac', function () {
spyOn(window, 'open').and.callFake(function (url, name) {
expect(url).toEqual(tabUrl);
- expect(name).toEqual('_blank');
+ expect(name).toEqual(windowTarget);
});
this.class.clickTab({
@@ -100,7 +128,7 @@ require('vendor/jquery.scrollTo');
it('opens page tab in a new browser tab with Middle-click - Mac/PC', function () {
spyOn(window, 'open').and.callFake(function (url, name) {
expect(url).toEqual(tabUrl);
- expect(name).toEqual('_blank');
+ expect(name).toEqual(windowTarget);
});
this.class.clickTab({
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 9b657868523..1d014502c2a 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -8,7 +8,7 @@ require('~/new_branch_form');
describe('Branch', function() {
return describe('create a new branch', function() {
var expectToHaveError, fillNameWith;
- preloadFixtures('static/new_branch.html.raw');
+ preloadFixtures('branches/new_branch.html.raw');
fillNameWith = function(value) {
return $('.js-branch-name').val(value).trigger('blur');
};
@@ -16,7 +16,7 @@ require('~/new_branch_form');
return expect($('.js-branch-name-error span').text()).toEqual(error);
};
beforeEach(function() {
- loadFixtures('static/new_branch.html.raw');
+ loadFixtures('branches/new_branch.html.raw');
$('form').on('submit', function(e) {
return e.preventDefault();
});
diff --git a/spec/javascripts/project_dashboard_spec.js.es6 b/spec/javascripts/project_dashboard_spec.js.es6
deleted file mode 100644
index 24833b4eb57..00000000000
--- a/spec/javascripts/project_dashboard_spec.js.es6
+++ /dev/null
@@ -1,86 +0,0 @@
-require('~/sidebar');
-
-(() => {
- describe('Project dashboard page', () => {
- let $pageWithSidebar = null;
- let $sidebarToggle = null;
- let sidebar = null;
- const fixtureTemplate = 'projects/dashboard.html.raw';
-
- const assertSidebarStateExpanded = (shouldBeExpanded) => {
- expect(sidebar.isExpanded).toBe(shouldBeExpanded);
- expect($pageWithSidebar.hasClass('page-sidebar-expanded')).toBe(shouldBeExpanded);
- };
-
- preloadFixtures(fixtureTemplate);
- beforeEach(() => {
- loadFixtures(fixtureTemplate);
-
- $pageWithSidebar = $('.page-with-sidebar');
- $sidebarToggle = $('.toggle-nav-collapse');
-
- // otherwise instantiating the Sidebar for the second time
- // won't do anything, as the Sidebar is a singleton class
- gl.Sidebar.singleton = null;
- sidebar = new gl.Sidebar();
- });
-
- it('can show the sidebar when the toggler is clicked', () => {
- assertSidebarStateExpanded(false);
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
- });
-
- it('should dismiss the sidebar when clone button clicked', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- const cloneButton = $('.project-clone-holder a.clone-dropdown-btn');
- cloneButton.click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when download button clicked', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- const downloadButton = $('.project-action-button .btn:has(i.fa-download)');
- downloadButton.click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when add button clicked', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- const addButton = $('.project-action-button .btn:has(i.fa-plus)');
- addButton.click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when notification button clicked', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- const notifButton = $('.js-notification-toggle-btns .notifications-btn');
- notifButton.click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when clicking on the body', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- $('body').click();
- assertSidebarStateExpanded(false);
- });
-
- it('should dismiss the sidebar when clicking on the project description header', () => {
- $sidebarToggle.click();
- assertSidebarStateExpanded(true);
-
- $('.project-home-panel').click();
- assertSidebarStateExpanded(false);
- });
- });
-})();
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index f7636865aa1..9284af8a8d9 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -34,7 +34,7 @@ require('~/extensions/jquery.js');
describe('RightSidebar', function() {
var fixtureName = 'issues/open-issue.html.raw';
preloadFixtures(fixtureName);
- loadJSONFixtures('todos.json');
+ loadJSONFixtures('todos/todos.json');
beforeEach(function() {
loadFixtures(fixtureName);
@@ -64,7 +64,7 @@ require('~/extensions/jquery.js');
});
it('should broadcast todo:toggle event when add todo clicked', function() {
- var todos = getJSONFixture('todos.json');
+ var todos = getJSONFixture('todos/todos.json');
spyOn(jQuery, 'ajax').and.callFake(function() {
var d = $.Deferred();
var response = todos;
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 030d2de090a..ca707d872a4 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -42,3 +42,38 @@ testsContext.keys().forEach(function (path) {
});
}
});
+
+// workaround: include all source files to find files with 0% coverage
+// see also https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15
+describe('Uncovered files', function () {
+ // the following files throw errors because of undefined variables
+ const troubleMakers = [
+ './blob_edit/blob_edit_bundle.js',
+ './cycle_analytics/components/stage_plan_component.js',
+ './cycle_analytics/components/stage_staging_component.js',
+ './cycle_analytics/components/stage_test_component.js',
+ './diff_notes/components/jump_to_discussion.js',
+ './diff_notes/components/resolve_count.js',
+ './merge_conflicts/components/inline_conflict_lines.js',
+ './merge_conflicts/components/parallel_conflict_lines.js',
+ './network/branch_graph.js',
+ ];
+
+ const sourceFiles = require.context('~', true, /^\.\/(?!application\.js).*\.(js|es6)$/);
+ sourceFiles.keys().forEach(function (path) {
+ // ignore if there is a matching spec file
+ if (testsContext.keys().indexOf(`${path.replace(/\.js(\.es6)?$/, '')}_spec`) > -1) {
+ return;
+ }
+
+ it(`includes '${path}'`, function () {
+ try {
+ sourceFiles(path);
+ } catch (err) {
+ if (troubleMakers.indexOf(path) === -1) {
+ expect(err).toBeNull();
+ }
+ }
+ });
+ });
+});
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
new file mode 100644
index 00000000000..66e4fbd6304
--- /dev/null
+++ b/spec/javascripts/todos_spec.js
@@ -0,0 +1,63 @@
+require('~/todos');
+require('~/lib/utils/common_utils');
+
+describe('Todos', () => {
+ preloadFixtures('todos/todos.html.raw');
+ let todoItem;
+
+ beforeEach(() => {
+ loadFixtures('todos/todos.html.raw');
+ todoItem = document.querySelector('.todos-list .todo');
+
+ return new gl.Todos();
+ });
+
+ describe('goToTodoUrl', () => {
+ it('opens the todo url', (done) => {
+ const todoLink = todoItem.dataset.url;
+
+ spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
+ expect(url).toEqual(todoLink);
+ done();
+ });
+
+ todoItem.click();
+ });
+
+ describe('meta click', () => {
+ let visitUrlSpy;
+
+ beforeEach(() => {
+ spyOn(gl.utils, 'isMetaClick').and.returnValue(true);
+ visitUrlSpy = spyOn(gl.utils, 'visitUrl').and.callFake(() => {});
+ });
+
+ it('opens the todo url in another tab', (done) => {
+ const todoLink = todoItem.dataset.url;
+
+ spyOn(window, 'open').and.callFake((url, target) => {
+ expect(todoLink).toEqual(url);
+ expect(target).toEqual('_blank');
+ done();
+ });
+
+ todoItem.click();
+ expect(visitUrlSpy).not.toHaveBeenCalled();
+ });
+
+ it('opens the avatar\'s url in another tab when the avatar is clicked', (done) => {
+ const avatarImage = todoItem.querySelector('img');
+ const avatarUrl = avatarImage.parentElement.getAttribute('href');
+
+ spyOn(window, 'open').and.callFake((url, target) => {
+ expect(avatarUrl).toEqual(url);
+ expect(target).toEqual('_blank');
+ done();
+ });
+
+ avatarImage.click();
+ expect(visitUrlSpy).not.toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6
index e84f0dcfe67..dd495cb43bc 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6
@@ -34,7 +34,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: '1' } });
expect(changeChanges.one).toEqual(1);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should go to the previous page', () => {
@@ -55,7 +55,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: 'Prev' } });
expect(changeChanges.one).toEqual(1);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should go to the next page', () => {
@@ -76,7 +76,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: 'Next' } });
expect(changeChanges.one).toEqual(5);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should go to the last page', () => {
@@ -97,7 +97,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: 'Last >>' } });
expect(changeChanges.one).toEqual(10);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should go to the first page', () => {
@@ -118,7 +118,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: '<< First' } });
expect(changeChanges.one).toEqual(1);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
it('should do nothing', () => {
@@ -139,7 +139,7 @@ describe('Pagination component', () => {
component.changePage({ target: { innerText: '...' } });
expect(changeChanges.one).toEqual(1);
- expect(changeChanges.two).toEqual('all');
+ expect(changeChanges.two).toEqual(null);
});
});
diff --git a/spec/lib/additional_email_headers_interceptor_spec.rb b/spec/lib/additional_email_headers_interceptor_spec.rb
new file mode 100644
index 00000000000..580450eef1e
--- /dev/null
+++ b/spec/lib/additional_email_headers_interceptor_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe AdditionalEmailHeadersInterceptor do
+ it 'adds Auto-Submitted header' do
+ mail = ActionMailer::Base.mail(to: 'test@mail.com', from: 'info@mail.com', body: 'hello').deliver
+
+ expect(mail.header['To'].value).to eq('test@mail.com')
+ expect(mail.header['From'].value).to eq('info@mail.com')
+ expect(mail.header['Auto-Submitted'].value).to eq('auto-generated')
+ expect(mail.header['X-Auto-Response-Suppress'].value).to eq('All')
+ end
+end
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 456dbac0698..11607d4fb26 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -311,7 +311,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
end
end
- describe '#issues_per_Project' do
+ describe '#issues_per_project' do
context 'using an internal issue tracker' do
it 'returns a Hash containing the issues per project' do
doc = Nokogiri::HTML.fragment('')
@@ -346,4 +346,26 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
end
end
end
+
+ describe '.references_in' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'yields valid references' do
+ expect do |b|
+ described_class.references_in(issue.to_reference, &b)
+ end.to yield_with_args(issue.to_reference, issue.iid, nil, nil, MatchData)
+ end
+
+ it "doesn't yield invalid references" do
+ expect do |b|
+ described_class.references_in('#0', &b)
+ end.not_to yield_control
+ end
+
+ it "doesn't yield unsupported references" do
+ expect do |b|
+ described_class.references_in(merge_request.to_reference, &b)
+ end.not_to yield_control
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 3e1ac9fb2b2..d5d128c1907 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -112,6 +112,19 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
end
end
+ context 'mentioning a nested group' do
+ it_behaves_like 'a reference containing an element node'
+
+ let(:group) { create(:group, :nested) }
+ let(:reference) { group.to_reference }
+
+ it 'links to the nested group' do
+ doc = reference_filter("Hey #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+ end
+ end
+
it 'links with adjacent text' do
doc = reference_filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index ba199917f5c..bca57105d1d 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -41,6 +41,29 @@ module Gitlab
render(input, context, asciidoc_opts)
end
end
+
+ context "XSS" do
+ links = {
+ 'links' => {
+ input: 'link:mylink"onmouseover="alert(1)[Click Here]',
+ output: "<div>\n<p><a href=\"mylink\">Click Here</a></p>\n</div>"
+ },
+ 'images' => {
+ input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]',
+ output: "<div>\n<p><span><img src=\"https://localhost.com/image.png\" alt=\"Alt text\"></span></p>\n</div>"
+ },
+ 'pre' => {
+ input: '```mypre"><script>alert(3)</script>',
+ output: "<div>\n<div>\n<pre lang=\"mypre\">\"&gt;<code></code></pre>\n</div>\n</div>"
+ }
+ }
+
+ links.each do |name, data|
+ it "does not convert dangerous #{name} into HTML" do
+ expect(render(data[:input], context)).to eql data[:output]
+ end
+ end
+ end
end
def render(*args)
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
new file mode 100644
index 00000000000..c455cd9b942
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::CycleAnalytics::BaseEventFetcher do
+ let(:max_events) { 2 }
+ let(:project) { create(:project) }
+ let(:user) { create(:user, :admin) }
+ let(:start_time_attrs) { Issue.arel_table[:created_at] }
+ let(:end_time_attrs) { [Issue::Metrics.arel_table[:first_associated_with_milestone_at]] }
+ let(:options) do
+ { start_time_attrs: start_time_attrs,
+ end_time_attrs: end_time_attrs,
+ from: 30.days.ago }
+ end
+
+ subject do
+ described_class.new(project: project,
+ stage: :issue,
+ options: options).fetch
+ end
+
+ before do
+ allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return(Issue.all)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:serialize) do |event|
+ event
+ end
+
+ stub_const('Gitlab::CycleAnalytics::BaseEventFetcher::MAX_EVENTS', max_events)
+
+ setup_events(count: 3)
+ end
+
+ it 'limits the rows to the max number' do
+ expect(subject.count).to eq(max_events)
+ end
+
+ def setup_events(count:)
+ count.times do
+ issue = create(:issue, project: project, created_at: 2.days.ago)
+ milestone = create(:milestone, project: project)
+
+ issue.update(milestone: milestone)
+ create_merge_request_closing_issue(issue)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 6c71e98066b..91c43f2bdc0 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -17,5 +17,31 @@ describe Gitlab::DataBuilder::Build do
it { expect(data[:build_allow_failure]).to eq(false) }
it { expect(data[:project_id]).to eq(build.project.id) }
it { expect(data[:project_name]).to eq(build.project.name_with_namespace) }
+
+ context 'commit author_url' do
+ context 'when no commit present' do
+ let(:build) { create(:ci_build) }
+
+ it 'sets to mailing address of git_author_email' do
+ expect(data[:commit][:author_url]).to eq("mailto:#{build.pipeline.git_author_email}")
+ end
+ end
+
+ context 'when commit present but has no author' do
+ let(:build) { create(:ci_build, :with_commit) }
+
+ it 'sets to mailing address of git_author_email' do
+ expect(data[:commit][:author_url]).to eq("mailto:#{build.pipeline.git_author_email}")
+ end
+ end
+
+ context 'when commit and author are present' do
+ let(:build) { create(:ci_build, :with_commit_and_author) }
+
+ it 'sets to GitLab user url' do
+ expect(data[:commit][:author_url]).to eq(Gitlab::Routing.url_helpers.user_url(username: build.commit.author.username))
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 7fd25b9e5bf..e94ca4fcfd2 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -12,15 +12,14 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
describe '#add_concurrent_index' do
context 'outside a transaction' do
before do
- expect(model).to receive(:transaction_open?).and_return(false)
-
- unless Gitlab::Database.postgresql?
- allow_any_instance_of(Gitlab::Database::MigrationHelpers).to receive(:disable_statement_timeout)
- end
+ allow(model).to receive(:transaction_open?).and_return(false)
end
context 'using PostgreSQL' do
- before { expect(Gitlab::Database).to receive(:postgresql?).and_return(true) }
+ before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ allow(model).to receive(:disable_statement_timeout)
+ end
it 'creates the index concurrently' do
expect(model).to receive(:add_index).
@@ -59,6 +58,71 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
end
end
+ describe '#add_concurrent_foreign_key' do
+ context 'inside a transaction' do
+ it 'raises an error' do
+ expect(model).to receive(:transaction_open?).and_return(true)
+
+ expect do
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+ end.to raise_error(RuntimeError)
+ end
+ end
+
+ context 'outside a transaction' do
+ before do
+ allow(model).to receive(:transaction_open?).and_return(false)
+ end
+
+ context 'using MySQL' do
+ it 'creates a regular foreign key' do
+ allow(Gitlab::Database).to receive(:mysql?).and_return(true)
+
+ expect(model).to receive(:add_foreign_key).
+ with(:projects, :users, column: :user_id, on_delete: :cascade)
+
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+ end
+ end
+
+ context 'using PostgreSQL' do
+ before do
+ allow(Gitlab::Database).to receive(:mysql?).and_return(false)
+ end
+
+ it 'creates a concurrent foreign key' do
+ expect(model).to receive(:disable_statement_timeout)
+ expect(model).to receive(:execute).ordered.with(/NOT VALID/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+ end
+ end
+ end
+ end
+
+ describe '#disable_statement_timeout' do
+ context 'using PostgreSQL' do
+ it 'disables statement timeouts' do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
+
+ expect(model).to receive(:execute).with('SET statement_timeout TO 0')
+
+ model.disable_statement_timeout
+ end
+ end
+
+ context 'using MySQL' do
+ it 'does nothing' do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
+
+ expect(model).not_to receive(:execute)
+
+ model.disable_statement_timeout
+ end
+ end
+ end
+
describe '#update_column_in_batches' do
before do
create_list(:empty_project, 5)
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index b142b3a2781..f01c42aff91 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -5,6 +5,12 @@ class MigrationTest
end
describe Gitlab::Database, lib: true do
+ describe '.adapter_name' do
+ it 'returns the name of the adapter' do
+ expect(described_class.adapter_name).to be_an_instance_of(String)
+ end
+ end
+
# These are just simple smoke tests to check if the methods work (regardless
# of what they may return).
describe '.mysql?' do
@@ -71,6 +77,54 @@ describe Gitlab::Database, lib: true do
end
end
+ describe '.with_connection_pool' do
+ it 'creates a new connection pool and disconnect it after used' do
+ closed_pool = nil
+
+ described_class.with_connection_pool(1) do |pool|
+ pool.with_connection do |connection|
+ connection.execute('SELECT 1 AS value')
+ end
+
+ expect(pool).to be_connected
+
+ closed_pool = pool
+ end
+
+ expect(closed_pool).not_to be_connected
+ end
+
+ it 'disconnects the pool even an exception was raised' do
+ error = Class.new(RuntimeError)
+ closed_pool = nil
+
+ begin
+ described_class.with_connection_pool(1) do |pool|
+ pool.with_connection do |connection|
+ connection.execute('SELECT 1 AS value')
+ end
+
+ closed_pool = pool
+
+ raise error.new('boom')
+ end
+ rescue error
+ end
+
+ expect(closed_pool).not_to be_connected
+ end
+ end
+
+ describe '.create_connection_pool' do
+ it 'creates a new connection pool with specific pool size' do
+ pool = described_class.create_connection_pool(5)
+
+ expect(pool)
+ .to be_kind_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
+ expect(pool.spec.config[:pool]).to eq(5)
+ end
+ end
+
describe '#true_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
index 53f7d244d88..20743811dab 100644
--- a/spec/lib/gitlab/import_export/import_export_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -2,14 +2,15 @@ require 'spec_helper'
describe Gitlab::ImportExport, services: true do
describe 'export filename' do
- let(:project) { create(:empty_project, :public, path: 'project-path') }
+ let(:group) { create(:group, :nested) }
+ let(:project) { create(:empty_project, :public, path: 'project-path', namespace: group) }
it 'contains the project path' do
expect(described_class.export_filename(project: project)).to include(project.path)
end
it 'contains the namespace path' do
- expect(described_class.export_filename(project: project)).to include(project.namespace.path)
+ expect(described_class.export_filename(project: project)).to include(project.namespace.full_path)
end
it 'does not go over a certain length' do
diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json
new file mode 100644
index 00000000000..a78836c3c34
--- /dev/null
+++ b/spec/lib/gitlab/import_export/project.light.json
@@ -0,0 +1,48 @@
+{
+ "description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
+ "visibility_level": 10,
+ "archived": false,
+ "labels": [
+ {
+ "id": 2,
+ "title": "test2",
+ "color": "#428bca",
+ "project_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "type": "ProjectLabel",
+ "priorities": [
+ ]
+ },
+ {
+ "id": 3,
+ "title": "test3",
+ "color": "#428bca",
+ "group_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "project_id": null,
+ "type": "GroupLabel",
+ "priorities": [
+ {
+ "id": 1,
+ "project_id": 5,
+ "label_id": 1,
+ "priority": 1,
+ "created_at": "2016-10-18T09:35:43.338Z",
+ "updated_at": "2016-10-18T09:35:43.338Z"
+ }
+ ]
+ }
+ ],
+ "snippets": [
+
+ ],
+ "hooks": [
+
+ ]
+} \ No newline at end of file
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 0af13ba8e47..94a3b0fbba9 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -3,24 +3,28 @@ include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do
- let(:user) { create(:user) }
- let(:namespace) { create(:namespace, owner: user) }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
- let!(:project) { create(:empty_project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
- let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
- let(:restored_project_json) { project_tree_restorer.restore }
+ before(:all) do
+ @user = create(:user)
- before do
- allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
+ RSpec::Mocks.with_temporary_scope do
+ @shared = Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path')
+ allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
+ @project = create(:empty_project, :builds_disabled, :issues_disabled, name: 'project', path: 'project')
+ project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project)
+ @restored_project_json = project_tree_restorer.restore
+ end
+ end
+
+ after(:all) do
+ @user.destroy!
end
context 'JSON' do
it 'restores models based on JSON' do
- expect(restored_project_json).to be true
+ expect(@restored_project_json).to be true
end
it 'restore correct project features' do
- restored_project_json
project = Project.find_by_path('project')
expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
@@ -31,62 +35,42 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
end
it 'has the same label associated to two issues' do
- restored_project_json
-
expect(ProjectLabel.find_by_title('test2').issues.count).to eq(2)
end
it 'has milestones associated to two separate issues' do
- restored_project_json
-
expect(Milestone.find_by_description('test milestone').issues.count).to eq(2)
end
it 'creates a valid pipeline note' do
- restored_project_json
-
expect(Ci::Pipeline.first.notes).not_to be_empty
end
it 'restores pipelines with missing ref' do
- restored_project_json
-
expect(Ci::Pipeline.where(ref: nil)).not_to be_empty
end
it 'restores the correct event with symbolised data' do
- restored_project_json
-
expect(Event.where.not(data: nil).first.data[:ref]).not_to be_empty
end
it 'preserves updated_at on issues' do
- restored_project_json
-
issue = Issue.where(description: 'Aliquam enim illo et possimus.').first
expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
end
it 'contains the merge access levels on a protected branch' do
- restored_project_json
-
expect(ProtectedBranch.first.merge_access_levels).not_to be_empty
end
it 'contains the push access levels on a protected branch' do
- restored_project_json
-
expect(ProtectedBranch.first.push_access_levels).not_to be_empty
end
context 'event at forth level of the tree' do
let(:event) { Event.where(title: 'test levels').first }
- before do
- restored_project_json
- end
-
it 'restores the event' do
expect(event).not_to be_nil
end
@@ -99,77 +83,40 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
it 'has the correct data for merge request st_diffs' do
# makes sure we are renaming the custom method +utf8_st_diffs+ into +st_diffs+
- expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9)
+ expect(MergeRequestDiff.where.not(st_diffs: nil).count).to eq(9)
end
it 'has labels associated to label links, associated to issues' do
- restored_project_json
-
expect(Label.first.label_links.first.target).not_to be_nil
end
it 'has project labels' do
- restored_project_json
-
expect(ProjectLabel.count).to eq(2)
end
it 'has no group labels' do
- restored_project_json
-
expect(GroupLabel.count).to eq(0)
end
- context 'with group' do
- let!(:project) do
- create(:empty_project,
- :builds_disabled,
- :issues_disabled,
- name: 'project',
- path: 'project',
- group: create(:group))
- end
-
- it 'has group labels' do
- restored_project_json
-
- expect(GroupLabel.count).to eq(1)
- end
-
- it 'has label priorities' do
- restored_project_json
-
- expect(GroupLabel.first.priorities).not_to be_empty
- end
- end
-
it 'has a project feature' do
- restored_project_json
-
- expect(project.project_feature).not_to be_nil
+ expect(@project.project_feature).not_to be_nil
end
it 'restores the correct service' do
- restored_project_json
-
expect(CustomIssueTrackerService.first).not_to be_nil
end
context 'Merge requests' do
- before do
- restored_project_json
- end
-
it 'always has the new project as a target' do
- expect(MergeRequest.find_by_title('MR1').target_project).to eq(project)
+ expect(MergeRequest.find_by_title('MR1').target_project).to eq(@project)
end
it 'has the same source project as originally if source/target are the same' do
- expect(MergeRequest.find_by_title('MR1').source_project).to eq(project)
+ expect(MergeRequest.find_by_title('MR1').source_project).to eq(@project)
end
it 'has the new project as target if source/target differ' do
- expect(MergeRequest.find_by_title('MR2').target_project).to eq(project)
+ expect(MergeRequest.find_by_title('MR2').target_project).to eq(@project)
end
it 'has no source if source/target differ' do
@@ -177,39 +124,71 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
end
end
- context 'project.json file access check' do
- it 'does not read a symlink' do
- Dir.mktmpdir do |tmpdir|
- setup_symlink(tmpdir, 'project.json')
- allow(shared).to receive(:export_path).and_call_original
-
- restored_project_json
+ context 'tokens are regenerated' do
+ it 'has a new CI trigger token' do
+ expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty
+ end
- expect(shared.errors.first).not_to include('test')
- end
+ it 'has a new CI build token' do
+ expect(Ci::Build.where(token: 'abcd')).to be_empty
end
end
+ end
+ end
- context 'when there is an existing build with build token' do
- it 'restores project json correctly' do
- create(:ci_build, token: 'abcd')
+ context 'Light JSON' do
+ let(:user) { create(:user) }
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
+ let!(:project) { create(:empty_project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
+ let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
+ let(:restored_project_json) { project_tree_restorer.restore }
- expect(restored_project_json).to be true
- end
- end
+ before do
+ allow(ImportExport).to receive(:project_filename).and_return('project.light.json')
+ allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
+ end
+
+ context 'project.json file access check' do
+ it 'does not read a symlink' do
+ Dir.mktmpdir do |tmpdir|
+ setup_symlink(tmpdir, 'project.json')
+ allow(shared).to receive(:export_path).and_call_original
- context 'tokens are regenerated' do
- before do
restored_project_json
- end
- it 'has a new CI trigger token' do
- expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty
+ expect(shared.errors.first).not_to include('test')
end
+ end
+ end
- it 'has a new CI build token' do
- expect(Ci::Build.where(token: 'abcd')).to be_empty
- end
+ context 'when there is an existing build with build token' do
+ it 'restores project json correctly' do
+ create(:ci_build, token: 'abcd')
+
+ expect(restored_project_json).to be true
+ end
+ end
+
+ context 'with group' do
+ let!(:project) do
+ create(:empty_project,
+ :builds_disabled,
+ :issues_disabled,
+ name: 'project',
+ path: 'project',
+ group: create(:group))
+ end
+
+ before do
+ restored_project_json
+ end
+
+ it 'has group labels' do
+ expect(GroupLabel.count).to eq(1)
+ end
+
+ it 'has label priorities' do
+ expect(GroupLabel.first.priorities).not_to be_empty
end
end
end
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
new file mode 100644
index 00000000000..168a59e5139
--- /dev/null
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::RepoRestorer, services: true do
+ describe 'bundle a project Git repo' do
+ let(:user) { create(:user) }
+ let!(:project_with_repo) { create(:project, :test_repo, name: 'test-repo-restorer', path: 'test-repo-restorer') }
+ let!(:project) { create(:empty_project) }
+ let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
+ let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
+ let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
+ let(:restorer) do
+ described_class.new(path_to_bundle: bundle_path,
+ shared: shared,
+ project: project)
+ end
+
+ before do
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+
+ bundler.save
+ end
+
+ after do
+ FileUtils.rm_rf(export_path)
+ FileUtils.rm_rf(project_with_repo.repository.path_to_repo)
+ FileUtils.rm_rf(project.repository.path_to_repo)
+ end
+
+ it 'restores the repo successfully' do
+ expect(restorer.restore).to be true
+ end
+
+ it 'has the webhooks' do
+ restorer.restore
+
+ expect(Gitlab::Git::Hook.new('post-receive', project.repository.path_to_repo)).to exist
+ end
+ end
+end
diff --git a/spec/lib/gitlab/other_markup.rb b/spec/lib/gitlab/other_markup.rb
new file mode 100644
index 00000000000..8f5a353b381
--- /dev/null
+++ b/spec/lib/gitlab/other_markup.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::OtherMarkup, lib: true do
+ context "XSS Checks" do
+ links = {
+ 'links' => {
+ file: 'file.rdoc',
+ input: 'XSS[JaVaScriPt:alert(1)]',
+ output: '<p><a>XSS</a></p>'
+ }
+ }
+ links.each do |name, data|
+ it "does not convert dangerous #{name} into HTML" do
+ expect(render(data[:file], data[:input], context)).to eql data[:output]
+ end
+ end
+ end
+
+ def render(*args)
+ described_class.render(*args)
+ end
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 6b689c41ef6..84cfd934fa0 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -42,14 +42,85 @@ describe Gitlab::ReferenceExtractor, lib: true do
> @offteam
})
+
expect(subject.users).to match_array([])
end
+ describe 'directly addressed users' do
+ before do
+ @u_foo = create(:user, username: 'foo')
+ @u_foo2 = create(:user, username: 'foo2')
+ @u_foo3 = create(:user, username: 'foo3')
+ @u_foo4 = create(:user, username: 'foo4')
+ @u_foo5 = create(:user, username: 'foo5')
+
+ @u_bar = create(:user, username: 'bar')
+ @u_bar2 = create(:user, username: 'bar2')
+ @u_bar3 = create(:user, username: 'bar3')
+ @u_bar4 = create(:user, username: 'bar4')
+
+ @u_tom = create(:user, username: 'tom')
+ @u_tom2 = create(:user, username: 'tom2')
+ end
+
+ context 'when a user is directly addressed' do
+ it 'accesses the user object which is mentioned in the beginning of the line' do
+ subject.analyze('@foo What do you think? cc: @bar, @tom')
+
+ expect(subject.directly_addressed_users).to match_array([@u_foo])
+ end
+
+ it "doesn't access the user object if it's not mentioned in the beginning of the line" do
+ subject.analyze('What do you think? cc: @bar')
+
+ expect(subject.directly_addressed_users).to be_empty
+ end
+ end
+
+ context 'when multiple users are addressed' do
+ it 'accesses the user objects which are mentioned in the beginning of the line' do
+ subject.analyze('@foo @bar What do you think? cc: @tom')
+
+ expect(subject.directly_addressed_users).to match_array([@u_foo, @u_bar])
+ end
+
+ it "doesn't access the user objects if they are not mentioned in the beginning of the line" do
+ subject.analyze('What do you think? cc: @foo @bar @tom')
+
+ expect(subject.directly_addressed_users).to be_empty
+ end
+ end
+
+ context 'when multiple users are addressed in different paragraphs' do
+ it 'accesses user objects which are mentioned in the beginning of each paragraph' do
+ subject.analyze <<-NOTE.strip_heredoc
+ @foo What do you think? cc: @tom
+
+ - @bar can you please have a look?
+
+ >>>
+ @foo2 what do you think? cc: @bar2
+ >>>
+
+ @foo3 @foo4 thank you!
+
+ > @foo5 well done!
+
+ 1. @bar3 Can you please check? cc: @tom2
+ 2. @bar4 What do you this of this MR?
+ NOTE
+
+ expect(subject.directly_addressed_users).to match_array([@u_foo, @u_foo3, @u_foo4])
+ end
+ end
+ end
+
it 'accesses valid issue objects' do
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.")
+
expect(subject.issues).to match_array([@i0, @i1])
end
@@ -58,6 +129,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
+
expect(subject.merge_requests).to match_array([@m1, @m0])
end
@@ -67,6 +139,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
@l2 = create(:label)
subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}")
+
expect(subject.labels).to match_array([@l0, @l1])
end
@@ -76,6 +149,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
@s2 = create(:project_snippet)
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}")
+
expect(subject.snippets).to match_array([@s0, @s1])
end
@@ -127,6 +201,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
it 'handles project issue references' do
subject.analyze("this refers issue #{issue.to_reference(project)}")
+
extracted = subject.issues
expect(extracted.size).to eq(1)
expect(extracted).to match_array([issue])
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index c78cd30157e..089ec4e2737 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -2,47 +2,64 @@
require 'spec_helper'
describe Gitlab::Regex, lib: true do
- describe 'project path regex' do
- it { expect('gitlab-ce').to match(Gitlab::Regex.project_path_regex) }
- it { expect('gitlab_git').to match(Gitlab::Regex.project_path_regex) }
- it { expect('_underscore.js').to match(Gitlab::Regex.project_path_regex) }
- it { expect('100px.com').to match(Gitlab::Regex.project_path_regex) }
- it { expect('?gitlab').not_to match(Gitlab::Regex.project_path_regex) }
- it { expect('git lab').not_to match(Gitlab::Regex.project_path_regex) }
- it { expect('gitlab.git').not_to match(Gitlab::Regex.project_path_regex) }
+ describe '.project_path_regex' do
+ subject { described_class.project_path_regex }
+
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('gitlab_git') }
+ it { is_expected.to match('_underscore.js') }
+ it { is_expected.to match('100px.com') }
+ it { is_expected.not_to match('?gitlab') }
+ it { is_expected.not_to match('git lab') }
+ it { is_expected.not_to match('gitlab.git') }
end
- describe 'project name regex' do
- it { expect('gitlab-ce').to match(Gitlab::Regex.project_name_regex) }
- it { expect('GitLab CE').to match(Gitlab::Regex.project_name_regex) }
- it { expect('100 lines').to match(Gitlab::Regex.project_name_regex) }
- it { expect('gitlab.git').to match(Gitlab::Regex.project_name_regex) }
- it { expect('Český název').to match(Gitlab::Regex.project_name_regex) }
- it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) }
- it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
+ describe '.project_name_regex' do
+ subject { described_class.project_name_regex }
+
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('GitLab CE') }
+ it { is_expected.to match('100 lines') }
+ it { is_expected.to match('gitlab.git') }
+ it { is_expected.to match('Český název') }
+ it { is_expected.to match('Dash – is this') }
+ it { is_expected.not_to match('?gitlab') }
end
- describe 'file name regex' do
- it { expect('foo@bar').to match(Gitlab::Regex.file_name_regex) }
+ describe '.file_name_regex' do
+ subject { described_class.file_name_regex }
+
+ it { is_expected.to match('foo@bar') }
end
- describe 'file path regex' do
- it { expect('foo@/bar').to match(Gitlab::Regex.file_path_regex) }
+ describe '.file_path_regex' do
+ subject { described_class.file_path_regex }
+
+ it { is_expected.to match('foo@/bar') }
end
- describe 'environment slug regex' do
- def be_matched
- match(Gitlab::Regex.environment_slug_regex)
- end
+ describe '.environment_slug_regex' do
+ subject { described_class.environment_slug_regex }
+
+ it { is_expected.to match('foo') }
+ it { is_expected.to match('foo-1') }
+ it { is_expected.not_to match('FOO') }
+ it { is_expected.not_to match('foo/1') }
+ it { is_expected.not_to match('foo.1') }
+ it { is_expected.not_to match('foo*1') }
+ it { is_expected.not_to match('9foo') }
+ it { is_expected.not_to match('foo-') }
+ end
- it { expect('foo').to be_matched }
- it { expect('foo-1').to be_matched }
+ describe 'NAMESPACE_REF_REGEX_STR' do
+ subject { %r{\A#{Gitlab::Regex::NAMESPACE_REF_REGEX_STR}\z} }
- it { expect('FOO').not_to be_matched }
- it { expect('foo/1').not_to be_matched }
- it { expect('foo.1').not_to be_matched }
- it { expect('foo*1').not_to be_matched }
- it { expect('9foo').not_to be_matched }
- it { expect('foo-').not_to be_matched }
+ it { is_expected.to match('gitlab.org') }
+ it { is_expected.to match('gitlab.org/gitlab-git') }
+ it { is_expected.not_to match('gitlab.org.') }
+ it { is_expected.not_to match('gitlab.org/') }
+ it { is_expected.not_to match('/gitlab.org') }
+ it { is_expected.not_to match('gitlab.git') }
+ it { is_expected.not_to match('gitlab git') }
end
end
diff --git a/spec/lib/gitlab/slash_commands/extractor_spec.rb b/spec/lib/gitlab/slash_commands/extractor_spec.rb
index 1e4954c4af8..d7f77486b3e 100644
--- a/spec/lib/gitlab/slash_commands/extractor_spec.rb
+++ b/spec/lib/gitlab/slash_commands/extractor_spec.rb
@@ -81,6 +81,14 @@ describe Gitlab::SlashCommands::Extractor do
let(:original_msg) { "/assign @joe\nworld" }
let(:final_msg) { "world" }
end
+
+ it 'allows slash in command arguments' do
+ msg = "/assign @joe / @jane\nworld"
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to eq [['assign', '@joe / @jane']]
+ expect(msg).to eq 'world'
+ end
end
context 'in the middle of content' do
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
deleted file mode 100644
index 7a140518dd2..00000000000
--- a/spec/lib/gitlab/themes_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Themes, lib: true do
- describe '.body_classes' do
- it 'returns a space-separated list of class names' do
- css = described_class.body_classes
-
- expect(css).to include('ui_graphite')
- expect(css).to include(' ui_charcoal ')
- expect(css).to include(' ui_blue')
- end
- end
-
- describe '.by_id' do
- it 'returns a Theme by its ID' do
- expect(described_class.by_id(1).name).to eq 'Graphite'
- expect(described_class.by_id(6).name).to eq 'Blue'
- end
- end
-
- describe '.default' do
- it 'returns the default application theme' do
- allow(described_class).to receive(:default_id).and_return(2)
- expect(described_class.default.id).to eq 2
- end
-
- it 'prevents an infinite loop when configuration default is invalid' do
- default = described_class::APPLICATION_DEFAULT
- themes = described_class::THEMES
-
- config = double(default_theme: 0).as_null_object
- allow(Gitlab).to receive(:config).and_return(config)
- expect(described_class.default.id).to eq default
-
- config = double(default_theme: themes.size + 5).as_null_object
- allow(Gitlab).to receive(:config).and_return(config)
- expect(described_class.default.id).to eq default
- end
- end
-
- describe '.each' do
- it 'passes the block to the THEMES Array' do
- ids = []
- described_class.each { |theme| ids << theme.id }
- expect(ids).not_to be_empty
- end
- end
-end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 3f32248e52b..f8513ac8b1c 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -290,7 +290,7 @@ describe Ci::Runner, models: true do
let!(:last_update) { runner.ensure_runner_queue_value }
before do
- runner.update(description: 'new runner')
+ Ci::UpdateRunnerService.new(runner).update(description: 'new runner')
end
it 'sets a new last_update value' do
@@ -318,6 +318,25 @@ describe Ci::Runner, models: true do
end
end
+ describe '#destroy' do
+ let(:runner) { create(:ci_runner) }
+
+ context 'when there is a tick in the queue' do
+ let!(:queue_key) { runner.send(:runner_queue_key) }
+
+ before do
+ runner.tick_runner_queue
+ runner.destroy
+ end
+
+ it 'cleans up the queue' do
+ Gitlab::Redis.with do |redis|
+ expect(redis.get(queue_key)).to be_nil
+ end
+ end
+ end
+ end
+
describe '.assignable_for' do
let(:runner) { create(:ci_runner) }
let(:project) { create(:empty_project) }
diff --git a/spec/models/project_services/chat_message/build_message_spec.rb b/spec/models/project_services/chat_message/build_message_spec.rb
index 50ad5013df9..3bd7ec18ae0 100644
--- a/spec/models/project_services/chat_message/build_message_spec.rb
+++ b/spec/models/project_services/chat_message/build_message_spec.rb
@@ -11,21 +11,28 @@ describe ChatMessage::BuildMessage do
project_name: 'project_name',
project_url: 'http://example.gitlab.com',
+ build_id: 1,
+ build_name: build_name,
+ build_stage: stage,
commit: {
status: status,
author_name: 'hacker',
+ author_url: 'http://example.gitlab.com/hacker',
duration: duration,
},
}
end
let(:message) { build_message }
+ let(:stage) { 'test' }
+ let(:status) { 'success' }
+ let(:build_name) { 'rspec' }
+ let(:duration) { 10 }
context 'build succeeded' do
let(:status) { 'success' }
let(:color) { 'good' }
- let(:duration) { 10 }
let(:message) { build_message('passed') }
it 'returns a message with information about succeeded build' do
@@ -38,7 +45,6 @@ describe ChatMessage::BuildMessage do
context 'build failed' do
let(:status) { 'failed' }
let(:color) { 'danger' }
- let(:duration) { 10 }
it 'returns a message with information about failed build' do
expect(subject.pretext).to be_empty
@@ -47,11 +53,25 @@ describe ChatMessage::BuildMessage do
end
end
- def build_message(status_text = status)
+ it 'returns a message with information on build' do
+ expect(subject.fallback).to include("on build <http://example.gitlab.com/builds/1|#{build_name}>")
+ end
+
+ it 'returns a message with stage name' do
+ expect(subject.fallback).to include("of stage #{stage}")
+ end
+
+ it 'returns a message with link to author' do
+ expect(subject.fallback).to include("by <http://example.gitlab.com/hacker|hacker>")
+ end
+
+ def build_message(status_text = status, stage_text = stage, build_text = build_name)
"<http://example.gitlab.com|project_name>:" \
" Commit <http://example.gitlab.com/commit/" \
"97de212e80737a608d939f648d959671fb0a0142/builds|97de212e>" \
" of <http://example.gitlab.com/commits/develop|develop> branch" \
- " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
+ " by <http://example.gitlab.com/hacker|hacker> #{status_text}" \
+ " on build <http://example.gitlab.com/builds/1|#{build_text}>" \
+ " of stage #{stage_text} in #{duration} #{'second'.pluralize(duration)}"
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 35f3dd00870..b0087a9e15d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1852,8 +1852,8 @@ describe Project, models: true do
end
describe '#pages_url' do
- let(:group) { create :group, name: group_name }
- let(:project) { create :empty_project, namespace: group, name: project_name }
+ let(:group) { create :group, name: 'Group' }
+ let(:nested_group) { create :group, parent: group }
let(:domain) { 'Example.com' }
subject { project.pages_url }
@@ -1863,18 +1863,37 @@ describe Project, models: true do
allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
end
- context 'group page' do
- let(:group_name) { 'Group' }
- let(:project_name) { 'group.example.com' }
+ context 'top-level group' do
+ let(:project) { create :empty_project, namespace: group, name: project_name }
- it { is_expected.to eq("http://group.example.com") }
+ context 'group page' do
+ let(:project_name) { 'group.example.com' }
+
+ it { is_expected.to eq("http://group.example.com") }
+ end
+
+ context 'project page' do
+ let(:project_name) { 'Project' }
+
+ it { is_expected.to eq("http://group.example.com/project") }
+ end
end
- context 'project page' do
- let(:group_name) { 'Group' }
- let(:project_name) { 'Project' }
+ context 'nested group' do
+ let(:project) { create :empty_project, namespace: nested_group, name: project_name }
+ let(:expected_url) { "http://group.example.com/#{nested_group.path}/#{project.path}" }
- it { is_expected.to eq("http://group.example.com/project") }
+ context 'group page' do
+ let(:project_name) { 'group.example.com' }
+
+ it { is_expected.to eq(expected_url) }
+ end
+
+ context 'project page' do
+ let(:project_name) { 'Project' }
+
+ it { is_expected.to eq(expected_url) }
+ end
end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 9bfa6409607..838fd3754b2 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -351,6 +351,17 @@ describe Repository, models: true do
expect(blob.data).to eq('Changelog!')
end
+ it 'respects the autocrlf setting' do
+ repository.commit_file(user, 'hello.txt', "Hello,\r\nWorld",
+ message: 'Add hello world',
+ branch_name: 'master',
+ update: true)
+
+ blob = repository.blob_at('master', 'hello.txt')
+
+ expect(blob.data).to eq("Hello,\nWorld")
+ end
+
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
expect do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 7fd49c73b37..584a4facd94 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -19,6 +19,7 @@ describe User, models: true do
it { is_expected.to have_many(:project_members).dependent(:destroy) }
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(:events).dependent(:destroy) }
it { is_expected.to have_many(:recent_events).class_name('Event') }
it { is_expected.to have_many(:issues).dependent(:destroy) }
@@ -303,6 +304,34 @@ describe User, models: true do
end
end
+ shared_context 'user keys' do
+ let(:user) { create(:user) }
+ let!(:key) { create(:key, user: user) }
+ let!(:deploy_key) { create(:deploy_key, user: user) }
+ end
+
+ describe '#keys' do
+ include_context 'user keys'
+
+ context 'with key and deploy key stored' do
+ it 'returns stored key, but not deploy_key' do
+ expect(user.keys).to include key
+ expect(user.keys).not_to include deploy_key
+ end
+ end
+ end
+
+ describe '#deploy_keys' do
+ include_context 'user keys'
+
+ context 'with key and deploy key stored' do
+ it 'returns stored deploy key, but not normal key' do
+ expect(user.deploy_keys).to include deploy_key
+ expect(user.deploy_keys).not_to include key
+ end
+ end
+ end
+
describe '#confirm' do
before do
allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true)
@@ -553,18 +582,16 @@ describe User, models: true do
it "applies defaults to user" do
expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
- expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
expect(user.external).to be_falsey
end
end
describe 'with default overrides' do
- let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: 1) }
+ let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true) }
it "applies defaults to user" do
expect(user.projects_limit).to eq(123)
expect(user.can_create_group).to be_falsey
- expect(user.theme_id).to eq(1)
end
end
diff --git a/spec/models/wiki_directory_spec.rb b/spec/models/wiki_directory_spec.rb
new file mode 100644
index 00000000000..1caaa557085
--- /dev/null
+++ b/spec/models/wiki_directory_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+RSpec.describe WikiDirectory, models: true do
+ describe 'validations' do
+ subject { build(:wiki_directory) }
+
+ it { is_expected.to validate_presence_of(:slug) }
+ end
+
+ describe '#initialize' do
+ context 'when there are pages' do
+ let(:pages) { [build(:wiki_page)] }
+ let(:directory) { WikiDirectory.new('/path_up_to/dir', pages) }
+
+ it 'sets the slug attribute' do
+ expect(directory.slug).to eq('/path_up_to/dir')
+ end
+
+ it 'sets the pages attribute' do
+ expect(directory.pages).to eq(pages)
+ end
+ end
+
+ context 'when there are no pages' do
+ let(:directory) { WikiDirectory.new('/path_up_to/dir') }
+
+ it 'sets the slug attribute' do
+ expect(directory.slug).to eq('/path_up_to/dir')
+ end
+
+ it 'sets the pages attribute to an empty array' do
+ expect(directory.pages).to eq([])
+ end
+ end
+ end
+
+ describe '#to_partial_path' do
+ it 'returns the relative path to the partial to be used' do
+ directory = build(:wiki_directory)
+
+ expect(directory.to_partial_path).to eq('projects/wikis/wiki_directory')
+ end
+ end
+end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 5c34b1b0a30..753dc938c52 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -7,6 +7,75 @@ describe WikiPage, models: true do
subject { WikiPage.new(wiki) }
+ describe '.group_by_directory' do
+ context 'when there are no pages' do
+ it 'returns an empty array' do
+ expect(WikiPage.group_by_directory(nil)).to eq([])
+ expect(WikiPage.group_by_directory([])).to eq([])
+ end
+ end
+
+ context 'when there are pages' do
+ before do
+ create_page('dir_1/dir_1_1/page_3', 'content')
+ create_page('dir_1/page_2', 'content')
+ create_page('dir_2/page_5', 'content')
+ create_page('dir_2/page_4', 'content')
+ create_page('page_1', 'content')
+ end
+ let(:page_1) { wiki.find_page('page_1') }
+ let(:dir_1) do
+ WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')])
+ end
+ let(:dir_1_1) do
+ WikiDirectory.new('dir_1/dir_1_1', [wiki.find_page('dir_1/dir_1_1/page_3')])
+ end
+ let(:dir_2) do
+ pages = [wiki.find_page('dir_2/page_5'),
+ wiki.find_page('dir_2/page_4')]
+ WikiDirectory.new('dir_2', pages)
+ end
+
+ it 'returns an array with pages and directories' do
+ expected_grouped_entries = [page_1, dir_1, dir_1_1, dir_2]
+
+ grouped_entries = WikiPage.group_by_directory(wiki.pages)
+
+ grouped_entries.each_with_index do |page_or_dir, i|
+ expected_page_or_dir = expected_grouped_entries[i]
+ expected_slugs = get_slugs(expected_page_or_dir)
+ slugs = get_slugs(page_or_dir)
+
+ expect(slugs).to match_array(expected_slugs)
+ end
+ end
+
+ it 'returns an array sorted by alphabetical position' do
+ # Directories and pages within directories are sorted alphabetically.
+ # Pages at root come before everything.
+ expected_order = ['page_1', 'dir_1/page_2', 'dir_1/dir_1_1/page_3',
+ 'dir_2/page_4', 'dir_2/page_5']
+
+ grouped_entries = WikiPage.group_by_directory(wiki.pages)
+
+ actual_order =
+ grouped_entries.map do |page_or_dir|
+ get_slugs(page_or_dir)
+ end.
+ flatten
+ expect(actual_order).to eq(expected_order)
+ end
+ end
+ end
+
+ describe '.unhyphenize' do
+ it 'removes hyphens from a name' do
+ name = 'a-name--with-hyphens'
+
+ expect(WikiPage.unhyphenize(name)).to eq('a name with hyphens')
+ end
+ end
+
describe "#initialize" do
context "when initialized with an existing gollum page" do
before do
@@ -189,6 +258,26 @@ describe WikiPage, models: true do
end
end
+ describe '#directory' do
+ context 'when the page is at the root directory' do
+ it 'returns an empty string' do
+ create_page('file', 'content')
+ page = wiki.find_page('file')
+
+ expect(page.directory).to eq('')
+ end
+ end
+
+ context 'when the page is inside an actual directory' do
+ it 'returns the full directory hierarchy' do
+ create_page('dir_1/dir_1_1/file', 'content')
+ page = wiki.find_page('dir_1/dir_1_1/file')
+
+ expect(page.directory).to eq('dir_1/dir_1_1')
+ end
+ end
+ end
+
describe '#historical?' do
before do
create_page('Update', 'content')
@@ -221,6 +310,27 @@ describe WikiPage, models: true do
end
end
+ describe '#to_partial_path' do
+ it 'returns the relative path to the partial to be used' do
+ page = build(:wiki_page)
+
+ expect(page.to_partial_path).to eq('projects/wikis/wiki_page')
+ end
+ end
+
+ describe '#==' do
+ let(:original_wiki_page) { create(:wiki_page) }
+
+ it 'returns true for identical wiki page' do
+ expect(original_wiki_page).to eq(original_wiki_page)
+ end
+
+ it 'returns false for updated wiki page' do
+ updated_wiki_page = original_wiki_page.update("Updated content")
+ expect(original_wiki_page).not_to eq(updated_wiki_page)
+ end
+ end
+
private
def remove_temp_repo(path)
@@ -239,4 +349,12 @@ describe WikiPage, models: true do
page = wiki.wiki.paged(title)
wiki.wiki.delete_page(page, commit_details)
end
+
+ def get_slugs(page_or_dir)
+ if page_or_dir.is_a? WikiPage
+ [page_or_dir.slug]
+ else
+ page_or_dir.pages.present? ? page_or_dir.pages.map(&:slug) : []
+ end
+ end
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 5a3ffc284f2..3e66236f6ae 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -31,7 +31,18 @@ describe API::Branches, api: true do
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name)
- expect(json_response['commit']['id']).to eq(branch_sha)
+ json_commit = json_response['commit']
+ expect(json_commit['id']).to eq(branch_sha)
+ expect(json_commit).to have_key('short_id')
+ expect(json_commit).to have_key('title')
+ expect(json_commit).to have_key('message')
+ expect(json_commit).to have_key('author_name')
+ expect(json_commit).to have_key('author_email')
+ expect(json_commit).to have_key('authored_date')
+ expect(json_commit).to have_key('committer_name')
+ expect(json_commit).to have_key('committer_email')
+ expect(json_commit).to have_key('committed_date')
+ expect(json_commit).to have_key('parent_ids')
expect(json_response['merged']).to eq(false)
expect(json_response['protected']).to eq(false)
expect(json_response['developers_can_push']).to eq(false)
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index ee5865ad740..34abd72f455 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -159,6 +159,7 @@ describe API::CommitStatuses, api: true do
context: 'coverage',
ref: 'develop',
description: 'test',
+ coverage: 80.0,
target_url: 'http://gitlab.com/status' }
post api(post_url, developer), optional_params
@@ -170,6 +171,7 @@ describe API::CommitStatuses, api: true do
expect(json_response['status']).to eq('success')
expect(json_response['name']).to eq('coverage')
expect(json_response['ref']).to eq('develop')
+ expect(json_response['coverage']).to eq(80.0)
expect(json_response['description']).to eq('test')
expect(json_response['target_url']).to eq('http://gitlab.com/status')
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index af9028a8978..3eef10c0698 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -367,11 +367,21 @@ describe API::Commits, api: true do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response).to have_http_status(200)
- expect(json_response['id']).to eq(project.repository.commit.id)
- expect(json_response['title']).to eq(project.repository.commit.title)
- expect(json_response['stats']['additions']).to eq(project.repository.commit.stats.additions)
- expect(json_response['stats']['deletions']).to eq(project.repository.commit.stats.deletions)
- expect(json_response['stats']['total']).to eq(project.repository.commit.stats.total)
+ commit = project.repository.commit
+ expect(json_response['id']).to eq(commit.id)
+ expect(json_response['short_id']).to eq(commit.short_id)
+ expect(json_response['title']).to eq(commit.title)
+ expect(json_response['message']).to eq(commit.safe_message)
+ expect(json_response['author_name']).to eq(commit.author_name)
+ expect(json_response['author_email']).to eq(commit.author_email)
+ expect(json_response['authored_date']).to eq(commit.authored_date.iso8601(3))
+ expect(json_response['committer_name']).to eq(commit.committer_name)
+ expect(json_response['committer_email']).to eq(commit.committer_email)
+ expect(json_response['committed_date']).to eq(commit.committed_date.iso8601(3))
+ expect(json_response['parent_ids']).to eq(commit.parent_ids)
+ expect(json_response['stats']['additions']).to eq(commit.stats.additions)
+ expect(json_response['stats']['deletions']).to eq(commit.stats.deletions)
+ expect(json_response['stats']['total']).to eq(commit.stats.total)
end
it "returns a 404 error if not found" do
@@ -464,6 +474,20 @@ describe API::Commits, api: true do
expect(response).to have_http_status(401)
end
end
+
+ context 'when the commit is present on two projects' do
+ let(:forked_project) { create(:project, :repository, creator: user2, namespace: user2.namespace) }
+ let!(:forked_project_note) { create(:note_on_commit, author: user2, project: forked_project, commit_id: forked_project.repository.commit.id, note: 'a comment on a commit for fork') }
+
+ it 'returns the comments for the target project' do
+ get api("/projects/#{forked_project.id}/repository/commits/#{forked_project.repository.commit.id}/comments", user2)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['note']).to eq('a comment on a commit for fork')
+ expect(json_response.first['author']['id']).to eq(user2.id)
+ end
+ end
end
describe 'POST :id/repository/commits/:sha/cherry_pick' do
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
deleted file mode 100644
index 92ac4fd334d..00000000000
--- a/spec/requests/api/fork_spec.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'spec_helper'
-
-describe API::Projects, api: true do
- include ApiHelpers
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let(:admin) { create(:admin) }
- let(:group) { create(:group) }
- let(:group2) do
- group = create(:group, name: 'group2_name')
- group.add_owner(user2)
- group
- end
-
- describe 'POST /projects/fork/:id' do
- let(:project) do
- create(:project, :repository, creator: user, namespace: user.namespace)
- end
-
- before do
- project.add_reporter(user2)
- end
-
- context 'when authenticated' do
- it 'forks if user has sufficient access to project' do
- post api("/projects/fork/#{project.id}", user2)
-
- expect(response).to have_http_status(201)
- expect(json_response['name']).to eq(project.name)
- expect(json_response['path']).to eq(project.path)
- expect(json_response['owner']['id']).to eq(user2.id)
- expect(json_response['namespace']['id']).to eq(user2.namespace.id)
- expect(json_response['forked_from_project']['id']).to eq(project.id)
- end
-
- it 'forks if user is admin' do
- post api("/projects/fork/#{project.id}", admin)
-
- expect(response).to have_http_status(201)
- expect(json_response['name']).to eq(project.name)
- expect(json_response['path']).to eq(project.path)
- expect(json_response['owner']['id']).to eq(admin.id)
- expect(json_response['namespace']['id']).to eq(admin.namespace.id)
- expect(json_response['forked_from_project']['id']).to eq(project.id)
- end
-
- it 'fails on missing project access for the project to fork' do
- new_user = create(:user)
- post api("/projects/fork/#{project.id}", new_user)
-
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Project Not Found')
- end
-
- it 'fails if forked project exists in the user namespace' do
- post api("/projects/fork/#{project.id}", user)
-
- expect(response).to have_http_status(409)
- expect(json_response['message']['name']).to eq(['has already been taken'])
- expect(json_response['message']['path']).to eq(['has already been taken'])
- end
-
- it 'fails if project to fork from does not exist' do
- post api('/projects/fork/424242', user)
-
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Project Not Found')
- end
-
- it 'forks with explicit own user namespace id' do
- post api("/projects/fork/#{project.id}", user2), namespace: user2.namespace.id
-
- expect(response).to have_http_status(201)
- expect(json_response['owner']['id']).to eq(user2.id)
- end
-
- it 'forks with explicit own user name as namespace' do
- post api("/projects/fork/#{project.id}", user2), namespace: user2.username
-
- expect(response).to have_http_status(201)
- expect(json_response['owner']['id']).to eq(user2.id)
- end
-
- it 'forks to another user when admin' do
- post api("/projects/fork/#{project.id}", admin), namespace: user2.username
-
- expect(response).to have_http_status(201)
- expect(json_response['owner']['id']).to eq(user2.id)
- end
-
- it 'fails if trying to fork to another user when not admin' do
- post api("/projects/fork/#{project.id}", user2), namespace: admin.namespace.id
-
- expect(response).to have_http_status(404)
- end
-
- it 'fails if trying to fork to non-existent namespace' do
- post api("/projects/fork/#{project.id}", user2), namespace: 42424242
-
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Target Namespace Not Found')
- end
-
- it 'forks to owned group' do
- post api("/projects/fork/#{project.id}", user2), namespace: group2.name
-
- expect(response).to have_http_status(201)
- expect(json_response['namespace']['name']).to eq(group2.name)
- end
-
- it 'fails to fork to not owned group' do
- post api("/projects/fork/#{project.id}", user2), namespace: group.name
-
- expect(response).to have_http_status(404)
- end
-
- it 'forks to not owned group when admin' do
- post api("/projects/fork/#{project.id}", admin), namespace: group.name
-
- expect(response).to have_http_status(201)
- expect(json_response['namespace']['name']).to eq(group.name)
- end
- end
-
- context 'when unauthenticated' do
- it 'returns authentication error' do
- post api("/projects/fork/#{project.id}")
-
- expect(response).to have_http_status(401)
- expect(json_response['message']).to eq('401 Unauthorized')
- end
- end
- end
-end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index f78bde6f53a..ccd7898586c 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -338,6 +338,26 @@ describe API::Groups, api: true do
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project3.name)
end
+
+ it 'only returns the projects owned by user' do
+ project2.group.add_owner(user3)
+
+ get api("/groups/#{project2.group.id}/projects", user3), owned: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['name']).to eq(project2.name)
+ end
+
+ it 'only returns the projects starred by user' do
+ user1.starred_projects = [project1]
+
+ get api("/groups/#{group1.id}/projects", user1), starred: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['name']).to eq(project1.name)
+ end
end
context "when authenticated as admin" do
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 9892e014cb9..3e9bcfd1a60 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -145,11 +145,11 @@ describe API::Members, api: true do
end
end
- it "returns #{source_type == 'project' ? 201 : 409} if member already exists" do
+ it "returns 409 if member already exists" do
post api("/#{source_type.pluralize}/#{source.id}/members", master),
user_id: master.id, access_level: Member::MASTER
- expect(response).to have_http_status(source_type == 'project' ? 201 : 409)
+ expect(response).to have_http_status(409)
end
it 'returns 400 when user_id is not given' do
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 8beef821d6c..7bb208721a1 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -229,4 +229,40 @@ describe API::Milestones, api: true do
end
end
end
+
+ describe 'GET /projects/:id/milestones/:milestone_id/merge_requests' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ before do
+ milestone.merge_requests << merge_request
+ end
+
+ it 'returns project merge_requests for a particular milestone' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['title']).to eq(merge_request.title)
+ expect(json_response.first['milestone']['title']).to eq(milestone.title)
+ end
+
+ it 'returns a 404 error if milestone id not found' do
+ get api("/projects/#{project.id}/milestones/1234/merge_requests", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 404 if the user has no access to the milestone' do
+ new_user = create :user
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", new_user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 401 error if user not authenticated' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests")
+
+ expect(response).to have_http_status(401)
+ end
+ end
end
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index c1edf384d5c..a945d56f529 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -5,7 +5,7 @@ describe API::Namespaces, api: true do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let!(:group1) { create(:group) }
- let!(:group2) { create(:group) }
+ let!(:group2) { create(:group, :nested) }
describe "GET /namespaces" do
context "when unauthenticated" do
@@ -25,11 +25,13 @@ describe API::Namespaces, api: true do
end
it "admin: returns an array of matched namespaces" do
- get api("/namespaces?search=#{group1.name}", admin)
+ get api("/namespaces?search=#{group2.name}", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
+ expect(json_response.last['path']).to eq(group2.path)
+ expect(json_response.last['full_path']).to eq(group2.full_path)
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index ac0bbec44e0..741815a780e 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -41,26 +41,40 @@ describe API::Projects, api: true do
end
describe 'GET /projects' do
- before { project }
+ shared_examples_for 'projects response' do
+ it 'returns an array of projects' do
+ get api('/projects', current_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id))
+ end
+ end
+
+ let!(:public_project) { create(:empty_project, :public, name: 'public_project') }
+ before do
+ project
+ project2
+ project3
+ project4
+ end
context 'when unauthenticated' do
- it 'returns authentication error' do
- get api('/projects')
- expect(response).to have_http_status(401)
+ it_behaves_like 'projects response' do
+ let(:current_user) { nil }
+ let(:projects) { [public_project] }
end
end
context 'when authenticated as regular user' do
- it 'returns an array of projects' do
- get api('/projects', user)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first['name']).to eq(project.name)
- expect(json_response.first['owner']['username']).to eq(user.username)
+ it_behaves_like 'projects response' do
+ let(:current_user) { user }
+ let(:projects) { [public_project, project, project2, project3] }
end
it 'includes the project labels as the tag_list' do
get api('/projects', user)
+
expect(response.status).to eq 200
expect(json_response).to be_an Array
expect(json_response.first.keys).to include('tag_list')
@@ -68,21 +82,39 @@ describe API::Projects, api: true do
it 'includes open_issues_count' do
get api('/projects', user)
+
expect(response.status).to eq 200
expect(json_response).to be_an Array
expect(json_response.first.keys).to include('open_issues_count')
end
- it 'does not include open_issues_count' do
+ it 'does not include open_issues_count if issues are disabled' do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
get api('/projects', user)
+
expect(response.status).to eq 200
expect(json_response).to be_an Array
- expect(json_response.first.keys).not_to include('open_issues_count')
+ expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
+ end
+
+ it "does not include statistics by default" do
+ get api('/projects', user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first).not_to include('statistics')
end
- context 'GET /projects?simple=true' do
+ it "includes statistics if requested" do
+ get api('/projects', user), statistics: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first).to include 'statistics'
+ end
+
+ context 'and with simple=true' do
it 'returns a simplified version of all the projects' do
expected_keys = ["id", "http_url_to_repo", "web_url", "name", "name_with_namespace", "path", "path_with_namespace"]
@@ -97,6 +129,7 @@ describe API::Projects, api: true do
context 'and using search' do
it 'returns searched project' do
get api('/projects', user), { search: project.name }
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -106,196 +139,109 @@ describe API::Projects, api: true do
context 'and using the visibility filter' do
it 'filters based on private visibility param' do
get api('/projects', user), { visibility: 'private' }
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
- expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count)
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
end
it 'filters based on internal visibility param' do
+ project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)
+
get api('/projects', user), { visibility: 'internal' }
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
- expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count)
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
end
it 'filters based on public visibility param' do
get api('/projects', user), { visibility: 'public' }
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
- expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count)
+ expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
end
end
context 'and using sorting' do
- before do
- project2
- project3
- end
-
it 'returns the correct order when sorted by id' do
get api('/projects', user), { order_by: 'id', sort: 'desc' }
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(project3.id)
end
end
- end
- end
-
- describe 'GET /projects/all' do
- before { project }
-
- context 'when unauthenticated' do
- it 'returns authentication error' do
- get api('/projects/all')
- expect(response).to have_http_status(401)
- end
- end
- context 'when authenticated as regular user' do
- it 'returns authentication error' do
- get api('/projects/all', user)
- expect(response).to have_http_status(403)
- end
- end
+ context 'and with owned=true' do
+ it 'returns an array of projects the user owns' do
+ get api('/projects', user4), owned: true
- context 'when authenticated as admin' do
- it 'returns an array of all projects' do
- get api('/projects/all', admin)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
-
- expect(json_response).to satisfy do |response|
- response.one? do |entry|
- entry.has_key?('permissions') &&
- entry['name'] == project.name &&
- entry['owner']['username'] == user.username
- end
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(project4.name)
+ expect(json_response.first['owner']['username']).to eq(user4.username)
end
end
- it "does not include statistics by default" do
- get api('/projects/all', admin)
-
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first).not_to include('statistics')
- end
-
- it "includes statistics if requested" do
- get api('/projects/all', admin), statistics: true
-
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first).to include 'statistics'
- end
- end
- end
-
- describe 'GET /projects/owned' do
- before do
- project3
- project4
- end
+ context 'and with starred=true' do
+ let(:public_project) { create(:empty_project, :public) }
- context 'when unauthenticated' do
- it 'returns authentication error' do
- get api('/projects/owned')
- expect(response).to have_http_status(401)
- end
- end
-
- context 'when authenticated as project owner' do
- it 'returns an array of projects the user owns' do
- get api('/projects/owned', user4)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first['name']).to eq(project4.name)
- expect(json_response.first['owner']['username']).to eq(user4.username)
- end
-
- it "does not include statistics by default" do
- get api('/projects/owned', user4)
-
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first).not_to include('statistics')
- end
-
- it "includes statistics if requested" do
- attributes = {
- commit_count: 23,
- storage_size: 702,
- repository_size: 123,
- lfs_objects_size: 234,
- build_artifacts_size: 345,
- }
-
- project4.statistics.update!(attributes)
+ before do
+ project_member2
+ user3.update_attributes(starred_projects: [project, project2, project3, public_project])
+ end
- get api('/projects/owned', user4), statistics: true
+ it 'returns the starred projects viewable by the user' do
+ get api('/projects', user3), starred: true
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first['statistics']).to eq attributes.stringify_keys
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
+ end
end
- end
- end
- describe 'GET /projects/visible' do
- shared_examples_for 'visible projects response' do
- it 'returns the visible projects' do
- get api('/projects/visible', current_user)
+ context 'and with all query parameters' do
+ # | | project5 | project6 | project7 | project8 | project9 |
+ # |---------+----------+----------+----------+----------+----------|
+ # | search | x | | x | x | x |
+ # | starred | x | x | | x | x |
+ # | public | x | x | x | | x |
+ # | owned | x | x | x | x | |
+ let!(:project5) { create(:empty_project, :public, path: 'gitlab5', namespace: user.namespace) }
+ let!(:project6) { create(:empty_project, :public, path: 'project6', namespace: user.namespace) }
+ let!(:project7) { create(:empty_project, :public, path: 'gitlab7', namespace: user.namespace) }
+ let!(:project8) { create(:empty_project, path: 'gitlab8', namespace: user.namespace) }
+ let!(:project9) { create(:empty_project, :public, path: 'gitlab9') }
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id))
- end
- end
-
- let!(:public_project) { create(:empty_project, :public) }
- before do
- project
- project2
- project3
- project4
- end
+ before do
+ user.update_attributes(starred_projects: [project5, project6, project8, project9])
+ end
- context 'when unauthenticated' do
- it_behaves_like 'visible projects response' do
- let(:current_user) { nil }
- let(:projects) { [public_project] }
- end
- end
+ it 'returns only projects that satify all query parameters' do
+ get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
- context 'when authenticated' do
- it_behaves_like 'visible projects response' do
- let(:current_user) { user }
- let(:projects) { [public_project, project, project2, project3] }
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(project5.id)
+ end
end
end
context 'when authenticated as a different user' do
- it_behaves_like 'visible projects response' do
+ it_behaves_like 'projects response' do
let(:current_user) { user2 }
let(:projects) { [public_project] }
end
end
- end
-
- describe 'GET /projects/starred' do
- let(:public_project) { create(:empty_project, :public) }
-
- before do
- project_member2
- user3.update_attributes(starred_projects: [project, project2, project3, public_project])
- end
- it 'returns the starred projects viewable by the user' do
- get api('/projects/starred', user3)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
+ context 'when authenticated as admin' do
+ it_behaves_like 'projects response' do
+ let(:current_user) { admin }
+ let(:projects) { Project.all }
+ end
end
end
@@ -639,6 +585,7 @@ describe API::Projects, api: true do
'name' => user.namespace.name,
'path' => user.namespace.path,
'kind' => user.namespace.kind,
+ 'full_path' => user.namespace.full_path,
})
end
@@ -1332,4 +1279,130 @@ describe API::Projects, api: true do
end
end
end
+
+ describe 'POST /projects/:id/fork' do
+ let(:project) do
+ create(:project, :repository, creator: user, namespace: user.namespace)
+ end
+ let(:group) { create(:group) }
+ let(:group2) do
+ group = create(:group, name: 'group2_name')
+ group.add_owner(user2)
+ group
+ end
+
+ before do
+ project.add_reporter(user2)
+ end
+
+ context 'when authenticated' do
+ it 'forks if user has sufficient access to project' do
+ post api("/projects/#{project.id}/fork", user2)
+
+ expect(response).to have_http_status(201)
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['path']).to eq(project.path)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ expect(json_response['namespace']['id']).to eq(user2.namespace.id)
+ expect(json_response['forked_from_project']['id']).to eq(project.id)
+ end
+
+ it 'forks if user is admin' do
+ post api("/projects/#{project.id}/fork", admin)
+
+ expect(response).to have_http_status(201)
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['path']).to eq(project.path)
+ expect(json_response['owner']['id']).to eq(admin.id)
+ expect(json_response['namespace']['id']).to eq(admin.namespace.id)
+ expect(json_response['forked_from_project']['id']).to eq(project.id)
+ end
+
+ it 'fails on missing project access for the project to fork' do
+ new_user = create(:user)
+ post api("/projects/#{project.id}/fork", new_user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+
+ it 'fails if forked project exists in the user namespace' do
+ post api("/projects/#{project.id}/fork", user)
+
+ expect(response).to have_http_status(409)
+ expect(json_response['message']['name']).to eq(['has already been taken'])
+ expect(json_response['message']['path']).to eq(['has already been taken'])
+ end
+
+ it 'fails if project to fork from does not exist' do
+ post api('/projects/424242/fork', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+
+ it 'forks with explicit own user namespace id' do
+ post api("/projects/#{project.id}/fork", user2), namespace: user2.namespace.id
+
+ expect(response).to have_http_status(201)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ end
+
+ it 'forks with explicit own user name as namespace' do
+ post api("/projects/#{project.id}/fork", user2), namespace: user2.username
+
+ expect(response).to have_http_status(201)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ end
+
+ it 'forks to another user when admin' do
+ post api("/projects/#{project.id}/fork", admin), namespace: user2.username
+
+ expect(response).to have_http_status(201)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ end
+
+ it 'fails if trying to fork to another user when not admin' do
+ post api("/projects/#{project.id}/fork", user2), namespace: admin.namespace.id
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'fails if trying to fork to non-existent namespace' do
+ post api("/projects/#{project.id}/fork", user2), namespace: 42424242
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Target Namespace Not Found')
+ end
+
+ it 'forks to owned group' do
+ post api("/projects/#{project.id}/fork", user2), namespace: group2.name
+
+ expect(response).to have_http_status(201)
+ expect(json_response['namespace']['name']).to eq(group2.name)
+ end
+
+ it 'fails to fork to not owned group' do
+ post api("/projects/#{project.id}/fork", user2), namespace: group.name
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'forks to not owned group when admin' do
+ post api("/projects/#{project.id}/fork", admin), namespace: group.name
+
+ expect(response).to have_http_status(201)
+ expect(json_response['namespace']['name']).to eq(group.name)
+ end
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ post api("/projects/#{project.id}/fork")
+
+ expect(response).to have_http_status(401)
+ expect(json_response['message']).to eq('401 Unauthorized')
+ end
+ end
+ end
end
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index f2d81a28cb8..daef9062e7d 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -183,6 +183,7 @@ describe API::Runners, api: true do
it 'updates runner' do
description = shared_runner.description
active = shared_runner.active
+ runner_queue_value = shared_runner.ensure_runner_queue_value
update_runner(shared_runner.id, admin, description: "#{description}_updated",
active: !active,
@@ -197,18 +198,24 @@ describe API::Runners, api: true do
expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
expect(shared_runner.run_untagged?).to be(false)
expect(shared_runner.locked?).to be(true)
+ expect(shared_runner.ensure_runner_queue_value)
+ .not_to eq(runner_queue_value)
end
end
context 'when runner is not shared' do
it 'updates runner' do
description = specific_runner.description
+ runner_queue_value = specific_runner.ensure_runner_queue_value
+
update_runner(specific_runner.id, admin, description: 'test')
specific_runner.reload
expect(response).to have_http_status(200)
expect(specific_runner.description).to eq('test')
expect(specific_runner.description).not_to eq(description)
+ expect(specific_runner.ensure_runner_queue_value)
+ .not_to eq(runner_queue_value)
end
end
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index d32ba60fc4c..c0a8c0832bb 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -3,23 +3,23 @@ require 'spec_helper'
describe API::Templates, api: true do
include ApiHelpers
- shared_examples_for 'the Template Entity' do |path|
- before { get api(path) }
+ context 'the Template Entity' do
+ before { get api('/templates/gitignores/Ruby') }
it { expect(json_response['name']).to eq('Ruby') }
it { expect(json_response['content']).to include('*.gem') }
end
-
- shared_examples_for 'the TemplateList Entity' do |path|
- before { get api(path) }
+
+ context 'the TemplateList Entity' do
+ before { get api('/templates/gitignores') }
it { expect(json_response.first['name']).not_to be_nil }
it { expect(json_response.first['content']).to be_nil }
end
- shared_examples_for 'requesting gitignores' do |path|
+ context 'requesting gitignores' do
it 'returns a list of available gitignore templates' do
- get api(path)
+ get api('/templates/gitignores')
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -27,9 +27,9 @@ describe API::Templates, api: true do
end
end
- shared_examples_for 'requesting gitlab-ci-ymls' do |path|
+ context 'requesting gitlab-ci-ymls' do
it 'returns a list of available gitlab_ci_ymls' do
- get api(path)
+ get api('/templates/gitlab_ci_ymls')
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -37,17 +37,17 @@ describe API::Templates, api: true do
end
end
- shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path|
+ context 'requesting gitlab-ci-yml for Ruby' do
it 'adds a disclaimer on the top' do
- get api(path)
+ get api('/templates/gitlab_ci_ymls/Ruby')
expect(response).to have_http_status(200)
expect(json_response['content']).to start_with("# This file is a template,")
end
end
- shared_examples_for 'the License Template Entity' do |path|
- before { get api(path) }
+ context 'the License Template Entity' do
+ before { get api('/templates/licenses/mit') }
it 'returns a license template' do
expect(json_response['key']).to eq('mit')
@@ -64,9 +64,9 @@ describe API::Templates, api: true do
end
end
- shared_examples_for 'GET licenses' do |path|
+ context 'GET templates/licenses' do
it 'returns a list of available license templates' do
- get api(path)
+ get api('/templates/licenses')
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -77,7 +77,7 @@ describe API::Templates, api: true do
describe 'the popular parameter' do
context 'with popular=1' do
it 'returns a list of available popular license templates' do
- get api("#{path}?popular=1")
+ get api('/templates/licenses?popular=1')
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -88,10 +88,10 @@ describe API::Templates, api: true do
end
end
- shared_examples_for 'GET licenses/:name' do |path|
+ context 'GET templates/licenses/:name' do
context 'with :project and :fullname given' do
before do
- get api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
+ get api("/templates/licenses/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
end
context 'for the mit license' do
@@ -178,26 +178,4 @@ describe API::Templates, api: true do
end
end
end
-
- describe 'with /templates namespace' do
- it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby'
- it_behaves_like 'the TemplateList Entity', '/templates/gitignores'
- it_behaves_like 'requesting gitignores', '/templates/gitignores'
- it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls'
- it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby'
- it_behaves_like 'the License Template Entity', '/templates/licenses/mit'
- it_behaves_like 'GET licenses', '/templates/licenses'
- it_behaves_like 'GET licenses/:name', '/templates/licenses'
- end
-
- describe 'without /templates namespace' do
- it_behaves_like 'the Template Entity', '/gitignores/Ruby'
- it_behaves_like 'the TemplateList Entity', '/gitignores'
- it_behaves_like 'requesting gitignores', '/gitignores'
- it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls'
- it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby'
- it_behaves_like 'the License Template Entity', '/licenses/mit'
- it_behaves_like 'GET licenses', '/licenses'
- it_behaves_like 'GET licenses/:name', '/licenses'
- end
end
diff --git a/spec/requests/api/v3/members_spec.rb b/spec/requests/api/v3/members_spec.rb
new file mode 100644
index 00000000000..28c3ca03960
--- /dev/null
+++ b/spec/requests/api/v3/members_spec.rb
@@ -0,0 +1,342 @@
+require 'spec_helper'
+
+describe API::Members, api: true do
+ include ApiHelpers
+
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:access_requester) { create(:user) }
+ let(:stranger) { create(:user) }
+
+ let(:project) do
+ create(:empty_project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project|
+ project.team << [developer, :developer]
+ project.team << [master, :master]
+ project.request_access(access_requester)
+ end
+ end
+
+ let!(:group) do
+ create(:group, :public, :access_requestable) do |group|
+ group.add_developer(developer)
+ group.add_owner(master)
+ group.request_access(access_requester)
+ end
+ end
+
+ shared_examples 'GET /:sources/:id/members' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { get v3_api("/#{source_type.pluralize}/#{source.id}/members", stranger) }
+ end
+
+ %i[master developer access_requester stranger].each do |type|
+ context "when authenticated as a #{type}" do
+ it 'returns 200' do
+ user = public_send(type)
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+ end
+ end
+ end
+
+ it 'does not return invitees' do
+ create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil)
+
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+ end
+
+ it 'finds members with query string' do
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
+
+ expect(response).to have_http_status(200)
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['username']).to eq(master.username)
+ end
+ end
+ end
+
+ shared_examples 'GET /:sources/:id/members/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { get v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-member' do
+ %i[access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 200' do
+ user = public_send(type)
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user)
+
+ expect(response).to have_http_status(200)
+ # User attributes
+ expect(json_response['id']).to eq(developer.id)
+ expect(json_response['name']).to eq(developer.name)
+ expect(json_response['username']).to eq(developer.username)
+ expect(json_response['state']).to eq(developer.state)
+ expect(json_response['avatar_url']).to eq(developer.avatar_url)
+ expect(json_response['web_url']).to eq(Gitlab::Routing.url_helpers.user_url(developer))
+
+ # Member attributes
+ expect(json_response['access_level']).to eq(Member::DEVELOPER)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ shared_examples 'POST /:sources/:id/members' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", stranger),
+ user_id: access_requester.id, access_level: Member::MASTER
+ end
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger developer].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", user),
+ user_id: access_requester.id, access_level: Member::MASTER
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ context 'and new member is already a requester' do
+ it 'transforms the requester into a proper member' do
+ expect do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: access_requester.id, access_level: Member::MASTER
+
+ expect(response).to have_http_status(201)
+ end.to change { source.members.count }.by(1)
+ expect(source.requesters.count).to eq(0)
+ expect(json_response['id']).to eq(access_requester.id)
+ expect(json_response['access_level']).to eq(Member::MASTER)
+ end
+ end
+
+ it 'creates a new member' do
+ expect do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05'
+
+ expect(response).to have_http_status(201)
+ end.to change { source.members.count }.by(1)
+ expect(json_response['id']).to eq(stranger.id)
+ expect(json_response['access_level']).to eq(Member::DEVELOPER)
+ expect(json_response['expires_at']).to eq('2016-08-05')
+ end
+ end
+
+ it "returns #{source_type == 'project' ? 201 : 409} if member already exists" do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: master.id, access_level: Member::MASTER
+
+ expect(response).to have_http_status(source_type == 'project' ? 201 : 409)
+ end
+
+ it 'returns 400 when user_id is not given' do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 400 when access_level is not given' do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: stranger.id
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 422 when access_level is not valid' do
+ post v3_api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: stranger.id, access_level: 1234
+
+ expect(response).to have_http_status(422)
+ end
+ end
+ end
+
+ shared_examples 'PUT /:sources/:id/members/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger),
+ access_level: Member::MASTER
+ end
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger developer].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'updates the member' do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
+ access_level: Member::MASTER, expires_at: '2016-08-05'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['id']).to eq(developer.id)
+ expect(json_response['access_level']).to eq(Member::MASTER)
+ expect(json_response['expires_at']).to eq('2016-08-05')
+ end
+ end
+
+ it 'returns 409 if member does not exist' do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/123", master),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns 400 when access_level is not given' do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master)
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 422 when access level is not valid' do
+ put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
+ access_level: 1234
+
+ expect(response).to have_http_status(422)
+ end
+ end
+ end
+
+ shared_examples 'DELETE /:sources/:id/members/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a member and deleting themself' do
+ it 'deletes the member' do
+ expect do
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", developer)
+
+ expect(response).to have_http_status(200)
+ end.to change { source.members.count }.by(-1)
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ context 'and member is a requester' do
+ it "returns #{source_type == 'project' ? 200 : 404}" do
+ expect do
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{access_requester.id}", master)
+
+ expect(response).to have_http_status(source_type == 'project' ? 200 : 404)
+ end.not_to change { source.requesters.count }
+ end
+ end
+
+ it 'deletes the member' do
+ expect do
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master)
+
+ expect(response).to have_http_status(200)
+ end.to change { source.members.count }.by(-1)
+ end
+ end
+
+ it "returns #{source_type == 'project' ? 200 : 404} if member does not exist" do
+ delete v3_api("/#{source_type.pluralize}/#{source.id}/members/123", master)
+
+ expect(response).to have_http_status(source_type == 'project' ? 200 : 404)
+ end
+ end
+ end
+
+ it_behaves_like 'GET /:sources/:id/members', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'GET /:sources/:id/members', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'GET /:sources/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'GET /:sources/:id/members/:user_id', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'POST /:sources/:id/members', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'POST /:sources/:id/members', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'PUT /:sources/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'PUT /:sources/:id/members/:user_id', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'group' do
+ let(:source) { group }
+ end
+
+ context 'Adding owner to project' do
+ it 'returns 403' do
+ expect do
+ post v3_api("/projects/#{project.id}/members", master),
+ user_id: stranger.id, access_level: Member::OWNER
+
+ expect(response).to have_http_status(422)
+ end.to change { project.members.count }.by(0)
+ end
+ end
+end
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index a495122bba7..36d99d80e79 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -682,6 +682,7 @@ describe API::V3::Projects, api: true do
'name' => user.namespace.name,
'path' => user.namespace.path,
'kind' => user.namespace.kind,
+ 'full_path' => user.namespace.full_path,
})
end
diff --git a/spec/requests/api/v3/templates_spec.rb b/spec/requests/api/v3/templates_spec.rb
new file mode 100644
index 00000000000..4fd4e70bedd
--- /dev/null
+++ b/spec/requests/api/v3/templates_spec.rb
@@ -0,0 +1,203 @@
+require 'spec_helper'
+
+describe API::V3::Templates, api: true do
+ include ApiHelpers
+
+ shared_examples_for 'the Template Entity' do |path|
+ before { get v3_api(path) }
+
+ it { expect(json_response['name']).to eq('Ruby') }
+ it { expect(json_response['content']).to include('*.gem') }
+ end
+
+ shared_examples_for 'the TemplateList Entity' do |path|
+ before { get v3_api(path) }
+
+ it { expect(json_response.first['name']).not_to be_nil }
+ it { expect(json_response.first['content']).to be_nil }
+ end
+
+ shared_examples_for 'requesting gitignores' do |path|
+ it 'returns a list of available gitignore templates' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to be > 15
+ end
+ end
+
+ shared_examples_for 'requesting gitlab-ci-ymls' do |path|
+ it 'returns a list of available gitlab_ci_ymls' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).not_to be_nil
+ end
+ end
+
+ shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path|
+ it 'adds a disclaimer on the top' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['content']).to start_with("# This file is a template,")
+ end
+ end
+
+ shared_examples_for 'the License Template Entity' do |path|
+ before { get v3_api(path) }
+
+ it 'returns a license template' do
+ expect(json_response['key']).to eq('mit')
+ expect(json_response['name']).to eq('MIT License')
+ expect(json_response['nickname']).to be_nil
+ expect(json_response['popular']).to be true
+ expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/')
+ expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT')
+ expect(json_response['description']).to include('A permissive license that is short and to the point.')
+ expect(json_response['conditions']).to eq(%w[include-copyright])
+ expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use])
+ expect(json_response['limitations']).to eq(%w[no-liability])
+ expect(json_response['content']).to include('The MIT License (MIT)')
+ end
+ end
+
+ shared_examples_for 'GET licenses' do |path|
+ it 'returns a list of available license templates' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(15)
+ expect(json_response.map { |l| l['key'] }).to include('agpl-3.0')
+ end
+
+ describe 'the popular parameter' do
+ context 'with popular=1' do
+ it 'returns a list of available popular license templates' do
+ get v3_api("#{path}?popular=1")
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(3)
+ expect(json_response.map { |l| l['key'] }).to include('apache-2.0')
+ end
+ end
+ end
+ end
+
+ shared_examples_for 'GET licenses/:name' do |path|
+ context 'with :project and :fullname given' do
+ before do
+ get v3_api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
+ end
+
+ context 'for the mit license' do
+ let(:license_type) { 'mit' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('The MIT License (MIT)')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the agpl-3.0 license' do
+ let(:license_type) { 'agpl-3.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the gpl-3.0 license' do
+ let(:license_type) { 'gpl-3.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the gpl-2.0 license' do
+ let(:license_type) { 'gpl-2.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the apache-2.0 license' do
+ let(:license_type) { 'apache-2.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('Apache License')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include("Copyright #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for an uknown license' do
+ let(:license_type) { 'muth-over9000' }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'with no :fullname given' do
+ context 'with an authenticated user' do
+ let(:user) { create(:user) }
+
+ it 'replaces the copyright owner placeholder with the name of the current user' do
+ get v3_api('/templates/licenses/mit', user)
+
+ expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}")
+ end
+ end
+ end
+ end
+
+ describe 'with /templates namespace' do
+ it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby'
+ it_behaves_like 'the TemplateList Entity', '/templates/gitignores'
+ it_behaves_like 'requesting gitignores', '/templates/gitignores'
+ it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls'
+ it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby'
+ it_behaves_like 'the License Template Entity', '/templates/licenses/mit'
+ it_behaves_like 'GET licenses', '/templates/licenses'
+ it_behaves_like 'GET licenses/:name', '/templates/licenses'
+ end
+
+ describe 'without /templates namespace' do
+ it_behaves_like 'the Template Entity', '/gitignores/Ruby'
+ it_behaves_like 'the TemplateList Entity', '/gitignores'
+ it_behaves_like 'requesting gitignores', '/gitignores'
+ it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls'
+ it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby'
+ it_behaves_like 'the License Template Entity', '/licenses/mit'
+ it_behaves_like 'GET licenses', '/licenses'
+ it_behaves_like 'GET licenses/:name', '/licenses'
+ end
+end
diff --git a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
new file mode 100644
index 00000000000..7cb24dc5646
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/migration/add_concurrent_foreign_key'
+
+describe RuboCop::Cop::Migration::AddConcurrentForeignKey do
+ include CopHelper
+
+ let(:cop) { described_class.new }
+
+ context 'outside of a migration' do
+ it 'does not register any offenses' do
+ inspect_source(cop, 'def up; add_foreign_key(:projects, :users, column: :user_id); end')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'in a migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when using add_foreign_key' do
+ inspect_source(cop, 'def up; add_foreign_key(:projects, :users, column: :user_id); end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+end
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 1b95f1ff198..6a6df377b35 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -181,6 +181,17 @@ describe EnvironmentSerializer do
expect(subject.first[:name]).to eq 'production'
expect(subject.second[:name]).to eq 'staging'
end
+
+ it 'appends correct total page count header' do
+ expect(subject).not_to be_empty
+ expect(response).to have_received(:[]=).with('X-Total', '3')
+ end
+
+ it 'appends correct page count headers' do
+ expect(subject).not_to be_empty
+ expect(response).to have_received(:[]=).with('X-Total-Pages', '2')
+ expect(response).to have_received(:[]=).with('X-Per-Page', '2')
+ end
end
end
end
diff --git a/spec/services/ci/update_runner_service_spec.rb b/spec/services/ci/update_runner_service_spec.rb
new file mode 100644
index 00000000000..e429fcfc72f
--- /dev/null
+++ b/spec/services/ci/update_runner_service_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Ci::UpdateRunnerService, :services do
+ let(:runner) { create(:ci_runner) }
+
+ describe '#update' do
+ before do
+ allow(runner).to receive(:tick_runner_queue)
+ end
+
+ context 'with description params' do
+ let(:params) { { description: 'new runner' } }
+
+ it 'updates the runner and ticking the queue' do
+ expect(update).to be_truthy
+
+ runner.reload
+
+ expect(runner).to have_received(:tick_runner_queue)
+ expect(runner.description).to eq('new runner')
+ end
+ end
+
+ context 'when params are not valid' do
+ let(:params) { { run_untagged: false } }
+
+ it 'does not update and give false because it is not valid' do
+ expect(update).to be_falsey
+
+ runner.reload
+
+ expect(runner).not_to have_received(:tick_runner_queue)
+ expect(runner.run_untagged).to be_truthy
+ end
+ end
+
+ def update
+ described_class.new(runner).update(params)
+ end
+ end
+end
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index f86189b68e9..32c2ed8cae7 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -9,14 +9,18 @@ describe Groups::DestroyService, services: true do
let!(:gitlab_shell) { Gitlab::Shell.new }
let!(:remove_path) { group.path + "+#{group.id}+deleted" }
+ before do
+ group.add_user(user, Gitlab::Access::OWNER)
+ end
+
shared_examples 'group destruction' do |async|
context 'database records' do
before do
destroy_group(group, user, async)
end
- it { expect(Group.all).not_to include(group) }
- it { expect(Project.all).not_to include(project) }
+ it { expect(Group.unscoped.all).not_to include(group) }
+ it { expect(Project.unscoped.all).not_to include(project) }
end
context 'file system' do
@@ -32,7 +36,7 @@ describe Groups::DestroyService, services: true do
context 'Sidekiq fake' do
before do
- # Dont run sidekiq to check if renamed repository exists
+ # Don't run sidekiq to check if renamed repository exists
Sidekiq::Testing.fake! { destroy_group(group, user, async) }
end
@@ -95,4 +99,13 @@ describe Groups::DestroyService, services: true do
describe 'synchronous delete' do
it_behaves_like 'group destruction', false
end
+
+ context 'projects in pending_delete' do
+ before do
+ project.pending_delete = true
+ project.save
+ end
+
+ it_behaves_like 'group destruction', false
+ end
end
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 4cfba35c830..09807e5d35b 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -120,11 +120,20 @@ describe Issues::BuildService, services: true do
end
describe '#execute' do
+ let(:milestone) { create(:milestone, project: project) }
+
it 'builds a new issues with given params' do
- issue = described_class.new(project, user, title: 'Issue #1', description: 'Issue description').execute
+ issue = described_class.new(
+ project,
+ user,
+ title: 'Issue #1',
+ description: 'Issue description',
+ milestone_id: milestone.id,
+ ).execute
expect(issue.title).to eq('Issue #1')
expect(issue.description).to eq('Issue description')
+ expect(issue.milestone).to eq(milestone)
end
end
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 30578ee4c7d..e1feeed8a67 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -46,6 +46,7 @@ describe Issues::CreateService, services: true do
expect(issue).to be_persisted
expect(issue.title).to eq('Awesome issue')
+ expect(issue.description).to eq('please fix')
expect(issue.assignee).to be_nil
expect(issue.labels).to be_empty
expect(issue.milestone).to be_nil
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 5a89acc96a4..d96f819e66a 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -149,35 +149,46 @@ describe MergeRequests::MergeService, services: true do
context "error handling" do
let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
- it 'saves error if there is an exception' do
- allow(service).to receive(:repository).and_raise("error message")
+ before do
+ allow(Rails.logger).to receive(:error)
+ end
+ it 'logs and saves error if there is an exception' do
+ error_message = 'error message'
+
+ allow(service).to receive(:repository).and_raise("error message")
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
- expect(merge_request.merge_error).to eq("Something went wrong during merge: error message")
+ expect(merge_request.merge_error).to include(error_message)
+ expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
- it 'saves error if there is an PreReceiveError exception' do
- allow(service).to receive(:repository).and_raise(GitHooksService::PreReceiveError, "error")
+ it 'logs and saves error if there is an PreReceiveError exception' do
+ error_message = 'error message'
+ allow(service).to receive(:repository).and_raise(GitHooksService::PreReceiveError, error_message)
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
- expect(merge_request.merge_error).to eq("error")
+ expect(merge_request.merge_error).to include(error_message)
+ expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
- it 'aborts if there is a merge conflict' do
+ it 'logs and saves error if there is a merge conflict' do
+ error_message = 'Conflicts detected during merge'
+
allow_any_instance_of(Repository).to receive(:merge).and_return(false)
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
- expect(merge_request.open?).to be_truthy
+ expect(merge_request).to be_open
expect(merge_request.merge_commit_sha).to be_nil
- expect(merge_request.merge_error).to eq("Conflicts detected during merge")
+ expect(merge_request.merge_error).to include(error_message)
+ expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 2cc21acab7b..983dac6efdb 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -287,41 +287,64 @@ describe MergeRequests::RefreshService, services: true do
it 'references the commit that caused the Work in Progress status' do
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
-
allow(refresh_service).to receive(:find_new_commits)
refresh_service.instance_variable_set("@commits", [
- instance_double(
- Commit,
+ double(
id: 'aaaaaaa',
+ sha: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e',
short_id: 'aaaaaaa',
title: 'Fix issue',
work_in_progress?: false
),
- instance_double(
- Commit,
+ double(
id: 'bbbbbbb',
+ sha: '498214de67004b1da3d820901307bed2a68a8ef6',
short_id: 'bbbbbbb',
title: 'fixup! Fix issue',
work_in_progress?: true,
to_reference: 'bbbbbbb'
),
- instance_double(
- Commit,
+ double(
id: 'ccccccc',
+ sha: '1b12f15a11fc6e62177bef08f47bc7b5ce50b141',
short_id: 'ccccccc',
title: 'fixup! Fix issue',
work_in_progress?: true,
to_reference: 'ccccccc'
),
])
-
refresh_service.execute(@oldrev, @newrev, 'refs/heads/wip')
reload_mrs
-
expect(@merge_request.notes.last.note).to eq(
"marked as a **Work In Progress** from bbbbbbb"
)
end
+
+ it 'does not mark as WIP based on commits that do not belong to an MR' do
+ allow(refresh_service).to receive(:find_new_commits)
+ refresh_service.instance_variable_set("@commits", [
+ double(
+ id: 'aaaaaaa',
+ sha: 'aaaaaaa',
+ short_id: 'aaaaaaa',
+ title: 'Fix issue',
+ work_in_progress?: false
+ ),
+ double(
+ id: 'bbbbbbb',
+ sha: 'bbbbbbbb',
+ short_id: 'bbbbbbb',
+ title: 'fixup! Fix issue',
+ work_in_progress?: true,
+ to_reference: 'bbbbbbb'
+ )
+ ])
+
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+
+ expect(@merge_request.work_in_progress?).to be_falsey
+ end
end
def reload_mrs
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 7cf2cd9968f..839250b7d84 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -146,6 +146,16 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
+ it "emails the note author if they've opted into notifications about their activity" do
+ add_users_with_subscription(note.project, issue)
+ note.author.notified_of_own_activity = true
+ reset_delivered_emails!
+
+ notification.new_note(note)
+
+ should_email(note.author)
+ end
+
it 'filters out "mentioned in" notes' do
mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author)
@@ -476,6 +486,20 @@ describe NotificationService, services: true do
should_not_email(issue.assignee)
end
+ it "emails the author if they've opted into notifications about their activity" do
+ issue.author.notified_of_own_activity = true
+
+ notification.new_issue(issue, issue.author)
+
+ should_email(issue.author)
+ end
+
+ it "doesn't email the author if they haven't opted into notifications about their activity" do
+ notification.new_issue(issue, issue.author)
+
+ should_not_email(issue.author)
+ end
+
it "emails subscribers of the issue's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -665,6 +689,19 @@ describe NotificationService, services: true do
should_email(subscriber_to_label_2)
end
+ it "emails the current user if they've opted into notifications about their activity" do
+ subscriber_to_label_2.notified_of_own_activity = true
+ notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
+
+ should_email(subscriber_to_label_2)
+ end
+
+ it "doesn't email the current user if they haven't opted into notifications about their activity" do
+ notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
+
+ should_not_email(subscriber_to_label_2)
+ end
+
it "doesn't send email to anyone but subscribers of the given labels" do
notification.relabeled_issue(issue, [group_label_2, label_2], @u_disabled)
@@ -818,6 +855,20 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
+ it "emails the author if they've opted into notifications about their activity" do
+ merge_request.author.notified_of_own_activity = true
+
+ notification.new_merge_request(merge_request, merge_request.author)
+
+ should_email(merge_request.author)
+ end
+
+ it "doesn't email the author if they haven't opted into notifications about their activity" do
+ notification.new_merge_request(merge_request, merge_request.author)
+
+ should_not_email(merge_request.author)
+ end
+
it "emails subscribers of the merge request's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -1013,6 +1064,14 @@ describe NotificationService, services: true do
should_not_email(@u_watcher)
end
+ it "notifies the merger when merge_when_build_succeeds is false but they've opted into notifications about their activity" do
+ merge_request.merge_when_build_succeeds = false
+ @u_watcher.notified_of_own_activity = true
+ notification.merge_mr(merge_request, @u_watcher)
+
+ should_email(@u_watcher)
+ end
+
it_behaves_like 'participating notifications' do
let(:participant) { create(:user, username: 'user-participant') }
let(:issuable) { merge_request }
diff --git a/spec/services/create_tag_service_spec.rb b/spec/services/tags/create_service_spec.rb
index 7dc43c50b0d..5478b8c9ec0 100644
--- a/spec/services/create_tag_service_spec.rb
+++ b/spec/services/tags/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CreateTagService, services: true do
+describe Tags::CreateService, services: true do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:user) { create(:user) }
diff --git a/spec/services/delete_tag_service_spec.rb b/spec/services/tags/destroy_service_spec.rb
index 477551f5036..a388c93379a 100644
--- a/spec/services/delete_tag_service_spec.rb
+++ b/spec/services/tags/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DeleteTagService, services: true do
+describe Tags::DestroyService, services: true do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:user) { create(:user) }
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 13d584a8975..4320365ab57 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -9,7 +9,9 @@ describe TodoService, services: true do
let(:admin) { create(:admin) }
let(:john_doe) { create(:user) }
let(:project) { create(:project) }
- let(:mentions) { [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') }
+ let(:mentions) { 'FYI: ' + [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') }
+ let(:directly_addressed) { [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') }
+ let(:directly_addressed_and_mentioned) { member.to_reference + ", what do you think? cc: " + [guest, admin].map(&:to_reference).join(' ') }
let(:service) { described_class.new }
before do
@@ -21,8 +23,10 @@ describe TodoService, services: true do
describe 'Issues' do
let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
+ let(:addressed_issue) { create(:issue, project: project, assignee: john_doe, author: author, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
let(:unassigned_issue) { create(:issue, project: project, assignee: nil) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: mentions) }
+ let(:addressed_confident_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: directly_addressed) }
describe '#new_issue' do
it 'creates a todo if assigned' do
@@ -52,6 +56,26 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.new_issue(addressed_issue, author)
+
+ should_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: guest, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
+ it 'creates correct todos for each valid user based on the type of mention' do
+ issue.update(description: directly_addressed_and_mentioned)
+
+ service.new_issue(issue, author)
+
+ should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
+ end
+
it 'does not create todo if user can not see the issue when issue is confidential' do
service.new_issue(confidential_issue, john_doe)
@@ -63,6 +87,17 @@ describe TodoService, services: true do
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
+ it 'does not create directly addressed todo if user cannot see the issue when issue is confidential' do
+ service.new_issue(addressed_confident_issue, john_doe)
+
+ should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::ASSIGNED)
+ should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
context 'when a private group is mentioned' do
let(:group) { create :group, :private }
let(:project) { create :project, :private, group: group }
@@ -94,12 +129,38 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
+ it 'creates a todo for each valid user based on the type of mention' do
+ issue.update(description: directly_addressed_and_mentioned)
+
+ service.update_issue(issue, author)
+
+ should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
+ end
+
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.update_issue(addressed_issue, author)
+
+ should_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: guest, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
it 'does not create a todo if user was already mentioned' do
create(:todo, :mentioned, user: member, project: project, target: issue, author: author)
expect { service.update_issue(issue, author) }.not_to change(member.todos, :count)
end
+ it 'does not create a directly addressed todo if user was already mentioned or addressed' do
+ create(:todo, :directly_addressed, user: member, project: project, target: addressed_issue, author: author)
+
+ expect { service.update_issue(addressed_issue, author) }.not_to change(member.todos, :count)
+ end
+
it 'does not create todo if user can not see the issue when issue is confidential' do
service.update_issue(confidential_issue, john_doe)
@@ -111,6 +172,17 @@ describe TodoService, services: true do
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
+ it 'does not create a directly addressed todo if user can not see the issue when issue is confidential' do
+ service.update_issue(addressed_confident_issue, john_doe)
+
+ should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
context 'issues with a task list' do
it 'does not create todo when tasks are marked as completed' do
issue.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")
@@ -125,6 +197,19 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
+ it 'does not create directly addressed todo when tasks are marked as completed' do
+ addressed_issue.update(description: "#{directly_addressed}\n- [x] Task 1\n- [x] Task 2\n")
+
+ service.update_issue(addressed_issue, author)
+
+ should_not_create_todo(user: admin, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: assignee, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
it 'does not raise an error when description not change' do
issue.update(title: 'Sample')
@@ -244,8 +329,11 @@ describe TodoService, services: true do
let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
+ let(:addressed_note) { create(:note, project: project, noteable: issue, author: john_doe, note: directly_addressed) }
let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
+ let(:addressed_note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: directly_addressed) }
let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: mentions) }
+ let(:addressed_note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: directly_addressed) }
let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
let(:system_note) { create(:system_note, project: project, noteable: issue) }
@@ -276,6 +364,26 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
end
+ it 'creates a todo for each valid user based on the type of mention' do
+ note.update(note: directly_addressed_and_mentioned)
+
+ service.new_note(note, john_doe)
+
+ should_create_todo(user: member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: note)
+ should_create_todo(user: admin, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ end
+
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.new_note(addressed_note, john_doe)
+
+ should_create_todo(user: member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ should_create_todo(user: author, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note)
+ end
+
it 'does not create todo if user can not see the issue when leaving a note on a confidential issue' do
service.new_note(note_on_confidential_issue, john_doe)
@@ -287,6 +395,17 @@ describe TodoService, services: true do
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
end
+ it 'does not create a directly addressed todo if user can not see the issue when leaving a note on a confidential issue' do
+ service.new_note(addressed_note_on_confidential_issue, john_doe)
+
+ should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
+ end
+
it 'creates a todo for each valid mentioned user when leaving a note on commit' do
service.new_note(note_on_commit, john_doe)
@@ -296,6 +415,15 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
end
+ it 'creates a directly addressed todo for each valid mentioned user when leaving a note on commit' do
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ end
+
it 'does not create todo when leaving a note on snippet' do
should_not_create_any_todo { service.new_note(note_on_project_snippet, john_doe) }
end
@@ -324,6 +452,7 @@ describe TodoService, services: true do
describe 'Merge Requests' do
let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
+ let(:addressed_mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") }
let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) }
describe '#new_merge_request' do
@@ -350,6 +479,25 @@ describe TodoService, services: true do
should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
+
+ it 'creates a todo for each valid user based on the type of mention' do
+ mr_assigned.update(description: directly_addressed_and_mentioned)
+
+ service.new_merge_request(mr_assigned, author)
+
+ should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
+ end
+
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.new_merge_request(addressed_mr_assigned, author)
+
+ should_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
end
describe '#update_merge_request' do
@@ -363,12 +511,37 @@ describe TodoService, services: true do
should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
+ it 'creates a todo for each valid user based on the type of mention' do
+ mr_assigned.update(description: directly_addressed_and_mentioned)
+
+ service.update_merge_request(mr_assigned, author)
+
+ should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
+ end
+
+ it 'creates a directly addressed todo for each valid addressed user' do
+ service.update_merge_request(addressed_mr_assigned, author)
+
+ should_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
it 'does not create a todo if user was already mentioned' do
create(:todo, :mentioned, user: member, project: project, target: mr_assigned, author: author)
expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count)
end
+ it 'does not create a directly addressed todo if user was already mentioned or addressed' do
+ create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr_assigned, author: author)
+
+ expect{ service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count)
+ end
+
context 'with a task list' do
it 'does not create todo when tasks are marked as completed' do
mr_assigned.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")
@@ -384,6 +557,20 @@ describe TodoService, services: true do
should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
end
+ it 'does not create directly addressed todo when tasks are marked as completed' do
+ addressed_mr_assigned.update(description: "#{directly_addressed}\n- [x] Task 1\n- [X] Task 2")
+
+ service.update_merge_request(addressed_mr_assigned, author)
+
+ should_not_create_todo(user: admin, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: assignee, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
+
it 'does not raise an error when description not change' do
mr_assigned.update(title: 'Sample')
@@ -436,6 +623,11 @@ describe TodoService, services: true do
service.reassigned_merge_request(mr_assigned, author)
should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
end
+
+ it 'does not create a directly addressed todo for guests' do
+ service.reassigned_merge_request(addressed_mr_assigned, author)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
end
describe '#merge_merge_request' do
@@ -452,6 +644,11 @@ describe TodoService, services: true do
service.merge_merge_request(mr_assigned, john_doe)
should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
end
+
+ it 'does not create directly addressed todo for guests' do
+ service.merge_merge_request(addressed_mr_assigned, john_doe)
+ should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
+ end
end
describe '#new_award_emoji' do
@@ -509,6 +706,7 @@ describe TodoService, services: true do
describe '#new_note' do
let(:mention) { john_doe.to_reference }
let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") }
+ let(:addressed_diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "#{mention}, hey!") }
let(:legacy_diff_note_on_merge_request) { create(:legacy_diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") }
it 'creates a todo for mentioned user on new diff note' do
@@ -517,6 +715,12 @@ describe TodoService, services: true do
should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: diff_note_on_merge_request)
end
+ it 'creates a directly addressed todo for addressed user on new diff note' do
+ service.new_note(addressed_diff_note_on_merge_request, author)
+
+ should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::DIRECTLY_ADDRESSED, note: addressed_diff_note_on_merge_request)
+ end
+
it 'creates a todo for mentioned user on legacy diff note' do
service.new_note(legacy_diff_note_on_merge_request, author)
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
new file mode 100644
index 00000000000..5341ba3d261
--- /dev/null
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe WikiPages::CreateService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:opts) do
+ {
+ title: 'Title',
+ content: 'Content for wiki page',
+ format: 'markdown'
+ }
+ end
+ let(:service) { described_class.new(project, user, opts) }
+
+ describe '#execute' do
+ context "valid params" do
+ before do
+ allow(service).to receive(:execute_hooks)
+ project.add_master(user)
+ end
+
+ subject { service.execute }
+
+ it 'creates a valid wiki page' do
+ is_expected.to be_valid
+ expect(subject.title).to eq(opts[:title])
+ expect(subject.content).to eq(opts[:content])
+ expect(subject.format).to eq(opts[:format].to_sym)
+ end
+
+ it 'executes webhooks' do
+ expect(service).to have_received(:execute_hooks).once.with(subject, 'create')
+ end
+ end
+ end
+end
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
new file mode 100644
index 00000000000..a4b9a390fe2
--- /dev/null
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe WikiPages::DestroyService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:wiki_page) { create(:wiki_page) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '#execute' do
+ before do
+ allow(service).to receive(:execute_hooks)
+ project.add_master(user)
+ end
+
+ it 'executes webhooks' do
+ service.execute(wiki_page)
+
+ expect(service).to have_received(:execute_hooks).once.with(wiki_page, 'delete')
+ end
+ end
+end
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
new file mode 100644
index 00000000000..2bccca764d7
--- /dev/null
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe WikiPages::UpdateService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:wiki_page) { create(:wiki_page) }
+ let(:opts) do
+ {
+ content: 'New content for wiki page',
+ format: 'markdown',
+ message: 'New wiki message'
+ }
+ end
+ let(:service) { described_class.new(project, user, opts) }
+
+ describe '#execute' do
+ context "valid params" do
+ before do
+ allow(service).to receive(:execute_hooks)
+ project.add_master(user)
+ end
+
+ subject { service.execute(wiki_page) }
+
+ it 'updates the wiki page' do
+ is_expected.to be_valid
+ expect(subject.content).to eq(opts[:content])
+ expect(subject.format).to eq(opts[:format].to_sym)
+ expect(subject.message).to eq(opts[:message])
+ end
+
+ it 'executes webhooks' do
+ expect(service).to have_received(:execute_hooks).once.with(subject, 'update')
+ end
+ end
+ end
+end
diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json
index ce8dfe5ae75..cd55d63125e 100644
--- a/spec/support/gitlab_stubs/session.json
+++ b/spec/support/gitlab_stubs/session.json
@@ -7,7 +7,7 @@
"skype":"aertert",
"linkedin":"",
"twitter":"",
- "theme_id":2,"color_scheme_id":2,
+ "color_scheme_id":2,
"state":"active",
"created_at":"2012-12-21T13:02:20Z",
"extern_uid":null,
@@ -17,4 +17,4 @@
"can_create_project":false,
"private_token":"Wvjy2Krpb7y8xi93owUz",
"access_token":"Wvjy2Krpb7y8xi93owUz"
-} \ No newline at end of file
+}
diff --git a/spec/support/gitlab_stubs/user.json b/spec/support/gitlab_stubs/user.json
index ce8dfe5ae75..cd55d63125e 100644
--- a/spec/support/gitlab_stubs/user.json
+++ b/spec/support/gitlab_stubs/user.json
@@ -7,7 +7,7 @@
"skype":"aertert",
"linkedin":"",
"twitter":"",
- "theme_id":2,"color_scheme_id":2,
+ "color_scheme_id":2,
"state":"active",
"created_at":"2012-12-21T13:02:20Z",
"extern_uid":null,
@@ -17,4 +17,4 @@
"can_create_project":false,
"private_token":"Wvjy2Krpb7y8xi93owUz",
"access_token":"Wvjy2Krpb7y8xi93owUz"
-} \ No newline at end of file
+}
diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb
new file mode 100644
index 00000000000..dac94dfc31e
--- /dev/null
+++ b/spec/support/issuables_list_metadata_shared_examples.rb
@@ -0,0 +1,35 @@
+shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
+ before do
+ @issuable_ids = []
+
+ 2.times do
+ if issuable_type == :issue
+ issuable = create(issuable_type, project: project)
+ else
+ issuable = create(issuable_type, title: FFaker::Lorem.sentence, source_project: project, source_branch: FFaker::Name.name)
+ end
+
+ @issuable_ids << issuable.id
+
+ issuable.id.times { create(:note, noteable: issuable, project: issuable.project) }
+ (issuable.id + 1).times { create(:award_emoji, :downvote, awardable: issuable) }
+ (issuable.id + 2).times { create(:award_emoji, :upvote, awardable: issuable) }
+ end
+ end
+
+ it "creates indexed meta-data object for issuable notes and votes count" do
+ if action
+ get action
+ else
+ get :index, namespace_id: project.namespace.path, project_id: project.path
+ end
+
+ meta_data = assigns(:issuable_meta_data)
+
+ @issuable_ids.each do |id|
+ expect(meta_data[id].notes_count).to eq(id)
+ expect(meta_data[id].downvotes).to eq(id + 1)
+ expect(meta_data[id].upvotes).to eq(id + 2)
+ end
+ end
+end
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
index dd54b0addda..c64574679b6 100644
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
@@ -58,7 +58,7 @@ shared_examples 'new issuable record that supports slash commands' do
let(:example_params) do
{
assignee: create(:user),
- milestone_id: double(:milestone),
+ milestone_id: 1,
description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}")
}
end
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
new file mode 100644
index 00000000000..f5381a48207
--- /dev/null
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe 'projects/_home_panel', :view do
+ let(:project) { create(:empty_project, :public) }
+
+ let(:notification_settings) do
+ user&.notification_settings_for(project)
+ end
+
+ before do
+ assign(:project, project)
+ assign(:notification_setting, notification_settings)
+
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:can?).and_return(false)
+ end
+
+ context 'when user is signed in' do
+ let(:user) { create(:user) }
+
+ it 'makes it possible to set notification level' do
+ render
+
+ expect(view).to render_template('shared/notifications/_button')
+ expect(rendered).to have_selector('.notification-dropdown')
+ end
+ end
+
+ context 'when user is signed out' do
+ let(:user) { nil }
+
+ it 'is not possible to set notification level' do
+ render
+
+ expect(rendered).not_to have_selector('.notification_dropdown')
+ end
+ end
+end
diff --git a/spec/views/projects/notes/_form.html.haml_spec.rb b/spec/views/projects/notes/_form.html.haml_spec.rb
index b14b1ece2d0..b61f016967f 100644
--- a/spec/views/projects/notes/_form.html.haml_spec.rb
+++ b/spec/views/projects/notes/_form.html.haml_spec.rb
@@ -21,7 +21,7 @@ describe 'projects/notes/_form' do
let(:note) { build(:"note_on_#{noteable}", project: project) }
it 'says that only markdown is supported, not slash commands' do
- expect(rendered).to have_content('Styling with Markdown and slash commands are supported')
+ expect(rendered).to have_content('Markdown and slash commands are supported')
end
end
end
@@ -30,7 +30,7 @@ describe 'projects/notes/_form' do
let(:note) { build(:note_on_commit, project: project) }
it 'says that only markdown is supported, not slash commands' do
- expect(rendered).to have_content('Styling with Markdown is supported')
+ expect(rendered).to have_content('Markdown is supported')
end
end
end