summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2017-06-01 17:42:38 +0800
committerLin Jen-Shin <godfat@godfat.org>2017-06-01 17:42:38 +0800
commit99b142bcf6be3c5f6a9265bb9d2ff43c6b6e2b0c (patch)
tree4275179c328e4763cde96c5e4b2b89023eac4866 /spec
parent9e60d57d9fec420ed2c68d6fe1d3e1af90abb4c5 (diff)
parentf74f03383f5ea1e6243eff1b091211e28c5ddbf1 (diff)
downloadgitlab-ce-99b142bcf6be3c5f6a9265bb9d2ff43c6b6e2b0c.tar.gz
Merge remote-tracking branch 'upstream/master' into 25680-CI_ENVIRONMENT_URL
* upstream/master: (251 commits) Don't match email addresses or foo@bar as user references Revert "Update GITLAB_SHELL_VERSION" Update GITLAB_SHELL_VERSION Add feature toggles through Flipper Change no_limits to limits Move includes call to scope Add GitLab Resources to University Add Documentation for GIT_CHECKOUT variable Remove entry variable Do not try to preload Commits when using Note.includes(:noteable) Ui improvements for count badges and permission badges Rename the other jobs Update jobs_spec for changes from builds_spec Introduce source to pipeline entity Update docs related to protected actions Add changelog for protected branches abilities fix Ask for an example project for bug reports Center loading spinner in issuable filters Fix chat commands specs related to protected actions Fix builds controller specs related to protected actions ...
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb22
-rw-r--r--spec/controllers/groups_controller_spec.rb2
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb48
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb4
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb2
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb (renamed from spec/controllers/projects/builds_controller_spec.rb)20
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb7
-rw-r--r--spec/factories/ci/pipelines.rb1
-rw-r--r--spec/factories/projects.rb12
-rw-r--r--spec/features/admin/admin_builds_spec.rb16
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb2
-rw-r--r--spec/features/admin/admin_system_info_spec.rb3
-rw-r--r--spec/features/dashboard/issues_spec.rb89
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb22
-rw-r--r--spec/features/dashboard/projects_spec.rb14
-rw-r--r--spec/features/groups/group_name_toggle_spec.rb4
-rw-r--r--spec/features/groups/members/list_spec.rb4
-rw-r--r--spec/features/groups_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb2
-rw-r--r--spec/features/issues/form_spec.rb60
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb17
-rw-r--r--spec/features/issues/move_spec.rb8
-rw-r--r--spec/features/merge_requests/edit_mr_spec.rb13
-rw-r--r--spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb10
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/merge_requests/widget_spec.rb21
-rw-r--r--spec/features/projects/artifacts/browse_spec.rb25
-rw-r--r--spec/features/projects/artifacts/download_spec.rb61
-rw-r--r--spec/features/projects/artifacts/file_spec.rb20
-rw-r--r--spec/features/projects/artifacts/raw_spec.rb25
-rw-r--r--spec/features/projects/developer_views_empty_project_instructions_spec.rb12
-rw-r--r--spec/features/projects/environments/environment_spec.rb57
-rw-r--r--spec/features/projects/group_links_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb (renamed from spec/features/projects/builds_spec.rb)161
-rw-r--r--spec/features/projects/members/sorting_spec.rb11
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb3
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb24
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb2
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_git_access_wiki_page_spec.rb2
-rw-r--r--spec/features/security/project/internal_access_spec.rb6
-rw-r--r--spec/features/security/project/private_access_spec.rb6
-rw-r--r--spec/features/security/project/public_access_spec.rb6
-rw-r--r--spec/finders/group_members_finder_spec.rb2
-rw-r--r--spec/finders/members_finder_spec.rb2
-rw-r--r--spec/finders/projects_finder_spec.rb15
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request.json3
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedule.json41
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedules.json4
-rw-r--r--spec/helpers/blob_helper_spec.rb28
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-rw-r--r--spec/helpers/submodule_helper_spec.rb5
-rw-r--r--spec/javascripts/build_spec.js308
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js3
-rw-r--r--spec/javascripts/fixtures/jobs.rb (renamed from spec/javascripts/fixtures/builds.rb)2
-rw-r--r--spec/javascripts/fixtures/raw.rb6
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js306
-rw-r--r--spec/javascripts/issue_show/components/edit_actions_spec.js147
-rw-r--r--spec/javascripts/issue_show/components/fields/description_spec.js56
-rw-r--r--spec/javascripts/issue_show/components/fields/description_template_spec.js49
-rw-r--r--spec/javascripts/issue_show/components/fields/project_move_spec.js38
-rw-r--r--spec/javascripts/issue_show/components/fields/title_spec.js30
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js68
-rw-r--r--spec/javascripts/issue_show/components/title_spec.js8
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js2
-rw-r--r--spec/javascripts/lib/utils/number_utility_spec.js9
-rw-r--r--spec/javascripts/merge_request_spec.js4
-rw-r--r--spec/javascripts/notebook/cells/markdown_spec.js57
-rw-r--r--spec/javascripts/notes_spec.js5
-rw-r--r--spec/javascripts/pipelines/graph/action_component_spec.js6
-rw-r--r--spec/javascripts/pipelines/graph/dropdown_action_component_spec.js4
-rw-r--r--spec/javascripts/pipelines/graph/graph_component_spec.js59
-rw-r--r--spec/javascripts/pipelines/graph/job_component_spec.js24
-rw-r--r--spec/javascripts/pipelines/pipeline_url_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js51
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js37
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js37
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js121
-rw-r--r--spec/javascripts/vue_shared/components/markdown/header_spec.js67
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb5
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb25
-rw-r--r--spec/lib/feature_spec.rb26
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb4
-rw-r--r--spec/lib/gitlab/chat_commands/command_spec.rb7
-rw-r--r--spec/lib/gitlab/chat_commands/deploy_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/status/build/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb10
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb26
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb24
-rw-r--r--spec/lib/gitlab/git/encoding_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb13
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb82
-rw-r--r--spec/lib/gitlab/group_hierarchy_spec.rb53
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb8
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/o_auth/provider_spec.rb42
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb73
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb4
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb49
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb9
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/migrations/fill_authorized_projects_spec.rb18
-rw-r--r--spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb66
-rw-r--r--spec/migrations/update_retried_for_ci_build_spec.rb (renamed from spec/migrations/update_retried_for_ci_builds_spec.rb)0
-rw-r--r--spec/models/blob_viewer/base_spec.rb70
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb8
-rw-r--r--spec/models/ci/pipeline_spec.rb26
-rw-r--r--spec/models/concerns/routable_spec.rb117
-rw-r--r--spec/models/environment_spec.rb5
-rw-r--r--spec/models/group_spec.rb2
-rw-r--r--spec/models/members/project_member_spec.rb13
-rw-r--r--spec/models/merge_request_spec.rb4
-rw-r--r--spec/models/milestone_spec.rb13
-rw-r--r--spec/models/namespace_spec.rb16
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb1
-rw-r--r--spec/models/project_spec.rb55
-rw-r--r--spec/models/project_team_spec.rb29
-rw-r--r--spec/models/project_wiki_spec.rb18
-rw-r--r--spec/models/user_spec.rb150
-rw-r--r--spec/policies/group_policy_spec.rb2
-rw-r--r--spec/requests/api/commit_statuses_spec.rb4
-rw-r--r--spec/requests/api/commits_spec.rb5
-rw-r--r--spec/requests/api/features_spec.rb104
-rw-r--r--spec/requests/api/groups_spec.rb2
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb297
-rw-r--r--spec/requests/api/pipelines_spec.rb26
-rw-r--r--spec/requests/api/projects_spec.rb28
-rw-r--r--spec/requests/api/v3/commits_spec.rb5
-rw-r--r--spec/requests/api/v3/groups_spec.rb2
-rw-r--r--spec/requests/api/v3/projects_spec.rb13
-rw-r--r--spec/rubocop/cop/migration/update_column_in_batches_spec.rb94
-rw-r--r--spec/serializers/build_action_entity_spec.rb12
-rw-r--r--spec/serializers/build_artifact_entity_spec.rb10
-rw-r--r--spec/serializers/build_entity_spec.rb6
-rw-r--r--spec/serializers/pipeline_entity_spec.rb4
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb20
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb2
-rw-r--r--spec/services/ci/play_build_service_spec.rb17
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb7
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb7
-rw-r--r--spec/services/git_push_service_spec.rb14
-rw-r--r--spec/services/git_tag_push_service_spec.rb14
-rw-r--r--spec/services/members/authorized_destroy_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_service_spec.rb31
-rw-r--r--spec/services/projects/destroy_service_spec.rb2
-rw-r--r--spec/services/search_service_spec.rb9
-rw-r--r--spec/services/system_note_service_spec.rb50
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb66
-rw-r--r--spec/spec_helper.rb8
-rw-r--r--spec/support/gitaly.rb3
-rw-r--r--spec/support/test_env.rb2
-rw-r--r--spec/validators/dynamic_path_validator_spec.rb22
-rw-r--r--spec/views/ci/status/_badge.html.haml_spec.rb2
-rw-r--r--spec/views/projects/blob/_viewer.html.haml_spec.rb4
-rw-r--r--spec/views/projects/jobs/_build.html.haml_spec.rb (renamed from spec/views/projects/builds/_build.html.haml_spec.rb)2
-rw-r--r--spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb (renamed from spec/views/projects/builds/_generic_commit_status.html.haml_spec.rb)0
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb (renamed from spec/views/projects/builds/show.html.haml_spec.rb)4
-rw-r--r--spec/workers/pipeline_schedule_worker_spec.rb3
161 files changed, 3525 insertions, 924 deletions
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 7d2f6dd9d0a..2c9d1ffc9c2 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -22,7 +22,7 @@ describe AutocompleteController do
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 1 }
+ it { expect(body.size).to eq 2 }
it { expect(body.map { |u| u["username"] }).to include(user.username) }
end
@@ -80,8 +80,8 @@ describe AutocompleteController do
end
it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 2 }
- it { expect(body.map { |u| u['username'] }).to match_array([user.username, non_member.username]) }
+ it { expect(body.size).to eq 3 }
+ it { expect(body.map { |u| u['username'] }).to include(user.username, non_member.username) }
end
end
@@ -97,6 +97,20 @@ describe AutocompleteController do
it { expect(body.size).to eq User.count }
end
+ context 'limited users per page' do
+ let(:per_page) { 2 }
+
+ before do
+ sign_in(user)
+ get(:users, per_page: per_page)
+ end
+
+ let(:body) { JSON.parse(response.body) }
+
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq per_page }
+ end
+
context 'unauthenticated user' do
let(:public_project) { create(:project, :public) }
let(:body) { JSON.parse(response.body) }
@@ -108,7 +122,7 @@ describe AutocompleteController do
end
it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 1 }
+ it { expect(body.size).to eq 2 }
end
describe 'GET #users with project' do
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 4626f1ebc29..b0b24b1de1b 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -26,7 +26,7 @@ describe GroupsController do
end
end
- describe 'GET #subgroups' do
+ describe 'GET #subgroups', :nested_groups do
let!(:public_subgroup) { create(:group, :public, parent: group) }
let!(:private_subgroup) { create(:group, :private, parent: group) }
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index eff9fab8da2..428bc45b842 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -12,7 +12,7 @@ describe Projects::ArtifactsController do
status: 'success')
end
- let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
before do
project.team << [user, :developer]
@@ -22,16 +22,16 @@ describe Projects::ArtifactsController do
describe 'GET download' do
it 'sends the artifacts file' do
- expect(controller).to receive(:send_file).with(build.artifacts_file.path, disposition: 'attachment').and_call_original
+ expect(controller).to receive(:send_file).with(job.artifacts_file.path, disposition: 'attachment').and_call_original
- get :download, namespace_id: project.namespace, project_id: project, build_id: build
+ get :download, namespace_id: project.namespace, project_id: project, job_id: job
end
end
describe 'GET browse' do
context 'when the directory exists' do
it 'renders the browse view' do
- get :browse, namespace_id: project.namespace, project_id: project, build_id: build, path: 'other_artifacts_0.1.2'
+ get :browse, namespace_id: project.namespace, project_id: project, job_id: job, path: 'other_artifacts_0.1.2'
expect(response).to render_template('projects/artifacts/browse')
end
@@ -39,7 +39,7 @@ describe Projects::ArtifactsController do
context 'when the directory does not exist' do
it 'responds Not Found' do
- get :browse, namespace_id: project.namespace, project_id: project, build_id: build, path: 'unknown'
+ get :browse, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
expect(response).to be_not_found
end
@@ -49,7 +49,7 @@ describe Projects::ArtifactsController do
describe 'GET file' do
context 'when the file exists' do
it 'renders the file view' do
- get :file, namespace_id: project.namespace, project_id: project, build_id: build, path: 'ci_artifacts.txt'
+ get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt'
expect(response).to render_template('projects/artifacts/file')
end
@@ -57,7 +57,7 @@ describe Projects::ArtifactsController do
context 'when the file does not exist' do
it 'responds Not Found' do
- get :file, namespace_id: project.namespace, project_id: project, build_id: build, path: 'unknown'
+ get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
expect(response).to be_not_found
end
@@ -67,7 +67,7 @@ describe Projects::ArtifactsController do
describe 'GET raw' do
context 'when the file exists' do
it 'serves the file using workhorse' do
- get :raw, namespace_id: project.namespace, project_id: project, build_id: build, path: 'ci_artifacts.txt'
+ get :raw, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt'
send_data = response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]
@@ -84,7 +84,7 @@ describe Projects::ArtifactsController do
context 'when the file does not exist' do
it 'responds Not Found' do
- get :raw, namespace_id: project.namespace, project_id: project, build_id: build, path: 'unknown'
+ get :raw, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
expect(response).to be_not_found
end
@@ -92,29 +92,29 @@ describe Projects::ArtifactsController do
end
describe 'GET latest_succeeded' do
- def params_from_ref(ref = pipeline.ref, job = build.name, path = 'browse')
+ def params_from_ref(ref = pipeline.ref, job_name = job.name, path = 'browse')
{
namespace_id: project.namespace,
project_id: project,
ref_name_and_path: File.join(ref, path),
- job: job
+ job: job_name
}
end
- context 'cannot find the build' do
+ context 'cannot find the job' do
shared_examples 'not found' do
it { expect(response).to have_http_status(:not_found) }
end
context 'has no such ref' do
before do
- get :latest_succeeded, params_from_ref('TAIL', build.name)
+ get :latest_succeeded, params_from_ref('TAIL', job.name)
end
it_behaves_like 'not found'
end
- context 'has no such build' do
+ context 'has no such job' do
before do
get :latest_succeeded, params_from_ref(pipeline.ref, 'NOBUILD')
end
@@ -124,20 +124,20 @@ describe Projects::ArtifactsController do
context 'has no path' do
before do
- get :latest_succeeded, params_from_ref(pipeline.sha, build.name, '')
+ get :latest_succeeded, params_from_ref(pipeline.sha, job.name, '')
end
it_behaves_like 'not found'
end
end
- context 'found the build and redirect' do
- shared_examples 'redirect to the build' do
+ context 'found the job and redirect' do
+ shared_examples 'redirect to the job' do
it 'redirects' do
- path = browse_namespace_project_build_artifacts_path(
+ path = browse_namespace_project_job_artifacts_path(
project.namespace,
project,
- build)
+ job)
expect(response).to redirect_to(path)
end
@@ -151,7 +151,7 @@ describe Projects::ArtifactsController do
get :latest_succeeded, params_from_ref('master')
end
- it_behaves_like 'redirect to the build'
+ it_behaves_like 'redirect to the job'
end
context 'with branch name containing slash' do
@@ -162,7 +162,7 @@ describe Projects::ArtifactsController do
get :latest_succeeded, params_from_ref('improve/awesome')
end
- it_behaves_like 'redirect to the build'
+ it_behaves_like 'redirect to the job'
end
context 'with branch name and path containing slashes' do
@@ -170,14 +170,14 @@ describe Projects::ArtifactsController do
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
- get :latest_succeeded, params_from_ref('improve/awesome', build.name, 'file/README.md')
+ get :latest_succeeded, params_from_ref('improve/awesome', job.name, 'file/README.md')
end
it 'redirects' do
- path = file_namespace_project_build_artifacts_path(
+ path = file_namespace_project_job_artifacts_path(
project.namespace,
project,
- build,
+ job,
'README.md')
expect(response).to redirect_to(path)
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 20f99b209eb..de13f17012b 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -177,7 +177,7 @@ describe Projects::EnvironmentsController do
expect(response).to have_http_status(200)
expect(json_response).to eq(
{ 'redirect_url' =>
- "http://test.host/#{project.path_with_namespace}/builds/#{action.id}" })
+ namespace_project_job_url(project.namespace, project, action) })
end
end
@@ -191,7 +191,7 @@ describe Projects::EnvironmentsController do
expect(response).to have_http_status(200)
expect(json_response).to eq(
{ 'redirect_url' =>
- "http://test.host/#{project.path_with_namespace}/environments/#{environment.id}" })
+ namespace_project_environment_url(project.namespace, project, environment) })
end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 04afd07c59e..a38ae2eb990 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -204,7 +204,7 @@ describe Projects::IssuesController do
body = JSON.parse(response.body)
expect(body['assignees'].first.keys)
- .to match_array(%w(id name username avatar_url))
+ .to match_array(%w(id name username avatar_url state web_url))
end
end
diff --git a/spec/controllers/projects/builds_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index f41503fd34e..838bdae1445 100644
--- a/spec/controllers/projects/builds_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::BuildsController do
+describe Projects::JobsController do
include ApiHelpers
let(:project) { create(:empty_project, :public) }
@@ -213,7 +213,7 @@ describe Projects::BuildsController do
it 'redirects to the retried build page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_build_path(id: Ci::Build.last.id))
+ expect(response).to redirect_to(namespace_project_job_path(id: Ci::Build.last.id))
end
end
@@ -234,7 +234,11 @@ describe Projects::BuildsController do
describe 'POST play' do
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
+
sign_in(user)
post_play
@@ -245,7 +249,7 @@ describe Projects::BuildsController do
it 'redirects to the played build page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_build_path(id: build.id))
+ expect(response).to redirect_to(namespace_project_job_path(id: build.id))
end
it 'transits to pending' do
@@ -281,7 +285,7 @@ describe Projects::BuildsController do
it 'redirects to the canceled build page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_build_path(id: build.id))
+ expect(response).to redirect_to(namespace_project_job_path(id: build.id))
end
it 'transits to canceled' do
@@ -319,7 +323,7 @@ describe Projects::BuildsController do
it 'redirects to a index page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_builds_path)
+ expect(response).to redirect_to(namespace_project_jobs_path)
end
it 'transits to canceled' do
@@ -336,7 +340,7 @@ describe Projects::BuildsController do
it 'redirects to a index page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_builds_path)
+ expect(response).to redirect_to(namespace_project_jobs_path)
end
end
@@ -359,7 +363,7 @@ describe Projects::BuildsController do
it 'redirects to the erased build page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_build_path(id: build.id))
+ expect(response).to redirect_to(namespace_project_job_path(id: build.id))
end
it 'erases artifacts' do
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 587a5820c6f..08024a2148b 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Projects::MergeRequestsController do
let(:project) { create(:project) }
- let(:user) { create(:user) }
+ let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
let(:merge_request_with_conflicts) do
create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr|
@@ -12,7 +12,6 @@ describe Projects::MergeRequestsController do
before do
sign_in(user)
- project.team << [user, :master]
end
describe 'GET new' do
@@ -304,6 +303,8 @@ describe Projects::MergeRequestsController do
end
context 'when user cannot access' do
+ let(:user) { create(:user) }
+
before do
project.add_reporter(user)
xhr :post, :merge, base_params
@@ -459,6 +460,8 @@ describe Projects::MergeRequestsController do
end
describe "DELETE destroy" do
+ let(:user) { create(:user) }
+
it "denies access to users unless they're admin or project owner" do
delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 361c5b9a49e..03e3c62effe 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -1,5 +1,6 @@
FactoryGirl.define do
factory :ci_empty_pipeline, class: Ci::Pipeline do
+ source :push
ref 'master'
sha '97de212e80737a608d939f648d959671fb0a0142'
status 'pending'
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 7a76f5f8afc..e8a9b688319 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -109,6 +109,18 @@ FactoryGirl.define do
merge_requests_access_level: merge_requests_access_level,
repository_access_level: evaluator.repository_access_level
)
+
+ # Normally the class Projects::CreateService is used for creating
+ # projects, and this class takes care of making sure the owner and current
+ # user have access to the project. Our specs don't use said service class,
+ # thus we must manually refresh things here.
+ owner = project.owner
+
+ if owner && owner.is_a?(User) && !project.pending_delete
+ project.members.create!(user: owner, access_level: Gitlab::Access::MASTER)
+ end
+
+ project.group&.refresh_members_authorized_projects
end
end
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 9d5ce876c29..999ce3611b5 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin Builds' do
create(:ci_build, pipeline: pipeline, status: :success)
create(:ci_build, pipeline: pipeline, status: :failed)
- visit admin_builds_path
+ visit admin_jobs_path
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_selector('.row-content-block', text: 'All jobs')
@@ -27,7 +27,7 @@ describe 'Admin Builds' do
context 'when have no jobs' do
it 'shows a message' do
- visit admin_builds_path
+ visit admin_jobs_path
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content 'No jobs to show'
@@ -44,7 +44,7 @@ describe 'Admin Builds' do
build3 = create(:ci_build, pipeline: pipeline, status: :success)
build4 = create(:ci_build, pipeline: pipeline, status: :failed)
- visit admin_builds_path(scope: :pending)
+ visit admin_jobs_path(scope: :pending)
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page.find('.build-link')).to have_content(build1.id)
@@ -59,7 +59,7 @@ describe 'Admin Builds' do
it 'shows a message' do
create(:ci_build, pipeline: pipeline, status: :success)
- visit admin_builds_path(scope: :pending)
+ visit admin_jobs_path(scope: :pending)
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page).to have_content 'No jobs to show'
@@ -76,7 +76,7 @@ describe 'Admin Builds' do
build3 = create(:ci_build, pipeline: pipeline, status: :failed)
build4 = create(:ci_build, pipeline: pipeline, status: :pending)
- visit admin_builds_path(scope: :running)
+ visit admin_jobs_path(scope: :running)
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page.find('.build-link')).to have_content(build1.id)
@@ -91,7 +91,7 @@ describe 'Admin Builds' do
it 'shows a message' do
create(:ci_build, pipeline: pipeline, status: :success)
- visit admin_builds_path(scope: :running)
+ visit admin_jobs_path(scope: :running)
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_content 'No jobs to show'
@@ -107,7 +107,7 @@ describe 'Admin Builds' do
build2 = create(:ci_build, pipeline: pipeline, status: :running)
build3 = create(:ci_build, pipeline: pipeline, status: :success)
- visit admin_builds_path(scope: :finished)
+ visit admin_jobs_path(scope: :finished)
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page.find('.build-link')).not_to have_content(build1.id)
@@ -121,7 +121,7 @@ describe 'Admin Builds' do
it 'shows a message' do
create(:ci_build, pipeline: pipeline, status: :running)
- visit admin_builds_path(scope: :finished)
+ visit admin_jobs_path(scope: :finished)
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No jobs to show'
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index 273cacd82cd..e8e080ce3e2 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -32,7 +32,7 @@ feature 'Admin disables Git access protocol', feature: true do
scenario 'shows only HTTP url' do
visit_project
- expect(page).to have_content("git clone #{project.http_url_to_repo(admin)}")
+ expect(page).to have_content("git clone #{project.http_url_to_repo}")
expect(page).not_to have_selector('#clone-dropdown')
end
end
diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb
index 1df972843e2..15482347886 100644
--- a/spec/features/admin/admin_system_info_spec.rb
+++ b/spec/features/admin/admin_system_info_spec.rb
@@ -20,6 +20,7 @@ describe 'Admin System Info' do
expect(page).to have_content 'CPU 2 cores'
expect(page).to have_content 'Memory 4 GB / 16 GB'
expect(page).to have_content 'Disks'
+ expect(page).to have_content 'Uptime'
end
end
@@ -34,6 +35,7 @@ describe 'Admin System Info' do
expect(page).to have_content 'CPU Unable to collect CPU info'
expect(page).to have_content 'Memory 4 GB / 16 GB'
expect(page).to have_content 'Disks'
+ expect(page).to have_content 'Uptime'
end
end
@@ -48,6 +50,7 @@ describe 'Admin System Info' do
expect(page).to have_content 'CPU 2 cores'
expect(page).to have_content 'Memory Unable to collect memory info'
expect(page).to have_content 'Disks'
+ expect(page).to have_content 'Uptime'
end
end
end
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 2346a9ec2ed..2cea6b1563e 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -2,66 +2,75 @@ require 'spec_helper'
RSpec.describe 'Dashboard Issues', feature: true do
let(:current_user) { create :user }
- let(:public_project) { create(:empty_project, :public) }
- let(:project) do
- create(:empty_project) do |project|
- project.team << [current_user, :master]
- end
- end
-
+ let!(:public_project) { create(:empty_project, :public) }
+ let(:project) { create(:empty_project) }
+ let(:project_with_issues_disabled) { create(:empty_project, :issues_disabled) }
let!(:authored_issue) { create :issue, author: current_user, project: project }
let!(:authored_issue_on_public_project) { create :issue, author: current_user, project: public_project }
let!(:assigned_issue) { create :issue, assignees: [current_user], project: project }
let!(:other_issue) { create :issue, project: project }
before do
+ [project, project_with_issues_disabled].each { |project| project.team << [current_user, :master] }
login_as(current_user)
-
visit issues_dashboard_path(assignee_id: current_user.id)
end
- it 'shows issues assigned to current user' do
- expect(page).to have_content(assigned_issue.title)
- expect(page).not_to have_content(authored_issue.title)
- expect(page).not_to have_content(other_issue.title)
- end
+ describe 'issues' do
+ it 'shows issues assigned to current user' do
+ expect(page).to have_content(assigned_issue.title)
+ expect(page).not_to have_content(authored_issue.title)
+ expect(page).not_to have_content(other_issue.title)
+ end
- it 'shows checkmark when unassigned is selected for assignee', js: true do
- find('.js-assignee-search').click
- find('li', text: 'Unassigned').click
- find('.js-assignee-search').click
+ it 'shows checkmark when unassigned is selected for assignee', js: true do
+ find('.js-assignee-search').click
+ find('li', text: 'Unassigned').click
+ find('.js-assignee-search').click
- expect(find('li[data-user-id="0"] a.is-active')).to be_visible
- end
+ expect(find('li[data-user-id="0"] a.is-active')).to be_visible
+ end
+
+ it 'shows issues when current user is author', js: true do
+ find('#assignee_id', visible: false).set('')
+ find('.js-author-search', match: :first).click
- it 'shows issues when current user is author', js: true do
- find('#assignee_id', visible: false).set('')
- find('.js-author-search', match: :first).click
+ expect(find('li[data-user-id="null"] a.is-active')).to be_visible
- expect(find('li[data-user-id="null"] a.is-active')).to be_visible
+ find('.dropdown-menu-author li a', match: :first, text: current_user.to_reference).click
+ find('.js-author-search', match: :first).click
- find('.dropdown-menu-author li a', match: :first, text: current_user.to_reference).click
- find('.js-author-search', match: :first).click
+ page.within '.dropdown-menu-user' do
+ expect(find('.dropdown-menu-author li a.is-active', match: :first, text: current_user.to_reference)).to be_visible
+ end
- page.within '.dropdown-menu-user' do
- expect(find('.dropdown-menu-author li a.is-active', match: :first, text: current_user.to_reference)).to be_visible
+ expect(page).to have_content(authored_issue.title)
+ expect(page).to have_content(authored_issue_on_public_project.title)
+ expect(page).not_to have_content(assigned_issue.title)
+ expect(page).not_to have_content(other_issue.title)
end
- expect(page).to have_content(authored_issue.title)
- expect(page).to have_content(authored_issue_on_public_project.title)
- expect(page).not_to have_content(assigned_issue.title)
- expect(page).not_to have_content(other_issue.title)
- end
+ it 'shows all issues' do
+ click_link('Reset filters')
- it 'shows all issues' do
- click_link('Reset filters')
+ expect(page).to have_content(authored_issue.title)
+ expect(page).to have_content(authored_issue_on_public_project.title)
+ expect(page).to have_content(assigned_issue.title)
+ expect(page).to have_content(other_issue.title)
+ end
- expect(page).to have_content(authored_issue.title)
- expect(page).to have_content(authored_issue_on_public_project.title)
- expect(page).to have_content(assigned_issue.title)
- expect(page).to have_content(other_issue.title)
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ describe 'new issue dropdown' do
+ it 'shows projects only with issues feature enabled', js: true do
+ find('.new-project-item-select-button').trigger('click')
+
+ page.within('.select2-results') do
+ expect(page).to have_content(project.name_with_namespace)
+ expect(page).not_to have_content(project_with_issues_disabled.name_with_namespace)
+ end
+ end
+ end
end
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 508ca38d7e5..9cebe52c444 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -2,16 +2,28 @@ require 'spec_helper'
describe 'Dashboard Merge Requests' do
let(:current_user) { create :user }
- let(:project) do
- create(:empty_project) do |project|
- project.add_master(current_user)
- end
- end
+ let(:project) { create(:empty_project) }
+ let(:project_with_merge_requests_disabled) { create(:empty_project, :merge_requests_disabled) }
before do
+ [project, project_with_merge_requests_disabled].each { |project| project.team << [current_user, :master] }
+
login_as(current_user)
end
+ describe 'new merge request dropdown' do
+ before { visit merge_requests_dashboard_path }
+
+ it 'shows projects only with merge requests feature enabled', js: true do
+ find('.new-project-item-select-button').trigger('click')
+
+ page.within('.select2-results') do
+ expect(page).to have_content(project.name_with_namespace)
+ expect(page).not_to have_content(project_with_merge_requests_disabled.name_with_namespace)
+ end
+ end
+ end
+
it 'should show an empty state' do
visit merge_requests_dashboard_path(assignee_id: current_user.id)
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 01351548a99..fa3435ab719 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -3,10 +3,11 @@ require 'spec_helper'
RSpec.describe 'Dashboard Projects', feature: true do
let(:user) { create(:user) }
let(:project) { create(:project, name: "awesome stuff") }
+ let(:project2) { create(:project, :public, name: 'Community project') }
before do
project.team << [user, :developer]
- login_as user
+ login_as(user)
end
it 'shows the project the user in a member of in the list' do
@@ -14,6 +15,17 @@ RSpec.describe 'Dashboard Projects', feature: true do
expect(page).to have_content('awesome stuff')
end
+ context 'when on Starred projects tab' do
+ it 'shows only starred projects' do
+ user.toggle_star(project2)
+
+ visit(starred_dashboard_projects_path)
+
+ expect(page).not_to have_content(project.name)
+ expect(page).to have_content(project2.name)
+ end
+ end
+
describe "with a pipeline", redis: true do
let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
diff --git a/spec/features/groups/group_name_toggle_spec.rb b/spec/features/groups/group_name_toggle_spec.rb
index 8a1d415c4f1..dfc3c84f29a 100644
--- a/spec/features/groups/group_name_toggle_spec.rb
+++ b/spec/features/groups/group_name_toggle_spec.rb
@@ -22,7 +22,7 @@ feature 'Group name toggle', feature: true, js: true do
expect(page).not_to have_css('.group-name-toggle')
end
- it 'is present if the title is longer than the container' do
+ it 'is present if the title is longer than the container', :nested_groups do
visit group_path(nested_group_3)
title_width = page.evaluate_script("$('.title')[0].offsetWidth")
@@ -35,7 +35,7 @@ feature 'Group name toggle', feature: true, js: true do
expect(title_width).to be > container_width
end
- it 'should show the full group namespace when toggled' do
+ it 'should show the full group namespace when toggled', :nested_groups do
page_height = page.current_window.size[1]
page.current_window.resize_to(SMALL_SCREEN, page_height)
visit group_path(nested_group_3)
diff --git a/spec/features/groups/members/list_spec.rb b/spec/features/groups/members/list_spec.rb
index 543879bd21d..f654fa16a06 100644
--- a/spec/features/groups/members/list_spec.rb
+++ b/spec/features/groups/members/list_spec.rb
@@ -12,7 +12,7 @@ feature 'Groups members list', feature: true do
login_as(user1)
end
- scenario 'show members from current group and parent' do
+ scenario 'show members from current group and parent', :nested_groups do
group.add_developer(user1)
nested_group.add_developer(user2)
@@ -22,7 +22,7 @@ feature 'Groups members list', feature: true do
expect(second_row.text).to include(user2.name)
end
- scenario 'show user once if member of both current group and parent' do
+ scenario 'show user once if member of both current group and parent', :nested_groups do
group.add_developer(user1)
nested_group.add_developer(user1)
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 3d32c47bf09..24ea7aba0cc 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -83,7 +83,7 @@ feature 'Group', feature: true do
end
end
- describe 'create a nested group', js: true do
+ describe 'create a nested group', :nested_groups, js: true do
let(:group) { create(:group, path: 'foo') }
context 'as admin' do
@@ -196,7 +196,7 @@ feature 'Group', feature: true do
end
end
- describe 'group page with nested groups', js: true do
+ describe 'group page with nested groups', :nested_groups, js: true do
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
let!(:path) { group_path(group) }
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 0b573d7cef4..4d38df05928 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -58,7 +58,7 @@ describe 'Dropdown assignee', :feature, :js do
it 'should load all the assignees when opened' do
filtered_search.set('assignee:')
- expect(dropdown_assignee_size).to eq(3)
+ expect(dropdown_assignee_size).to eq(4)
end
it 'shows current user at top of dropdown' do
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index b29177bed06..358b244fb5b 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -65,7 +65,7 @@ describe 'Dropdown author', js: true, feature: true do
it 'should load all the authors when opened' do
send_keys_to_filtered_search('author:')
- expect(dropdown_author_size).to eq(3)
+ expect(dropdown_author_size).to eq(4)
end
it 'shows current user at top of dropdown' do
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 009b3bc8bf6..8949dbcb663 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -3,6 +3,7 @@ require 'rails_helper'
describe 'New/edit issue', :feature, :js do
include GitlabRoutingHelper
include ActionView::Helpers::JavaScriptHelper
+ include FormHelper
let!(:project) { create(:project) }
let!(:user) { create(:user)}
@@ -23,6 +24,65 @@ describe 'New/edit issue', :feature, :js do
visit new_namespace_project_issue_path(project.namespace, project)
end
+ describe 'shorten users API pagination limit' do
+ before do
+ allow_any_instance_of(FormHelper).to receive(:issue_dropdown_options).and_wrap_original do |original, *args|
+ has_multiple_assignees = *args[1]
+
+ options = {
+ toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
+ title: 'Select assignee',
+ filter: true,
+ dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee',
+ placeholder: 'Search users',
+ data: {
+ per_page: 1,
+ null_user: true,
+ current_user: true,
+ project_id: project.try(:id),
+ field_name: "issue[assignee_ids][]",
+ default_label: 'Assignee',
+ 'max-select': 1,
+ 'dropdown-header': 'Assignee',
+ multi_select: true,
+ 'input-meta': 'name',
+ 'always-show-selectbox': true
+ }
+ }
+
+ if has_multiple_assignees
+ options[:title] = 'Select assignee(s)'
+ options[:data][:'dropdown-header'] = 'Assignee(s)'
+ options[:data].delete(:'max-select')
+ end
+
+ options
+ end
+
+ visit new_namespace_project_issue_path(project.namespace, project)
+
+ click_button 'Unassigned'
+
+ wait_for_requests
+ end
+
+ it 'should display selected users even if they are not part of the original API call' do
+ find('.dropdown-input-field').native.send_keys user2.name
+
+ page.within '.dropdown-menu-user' do
+ expect(page).to have_content user2.name
+ click_link user2.name
+ end
+
+ find('.js-dropdown-input-clear').click
+
+ page.within '.dropdown-menu-user' do
+ expect(page).to have_content user.name
+ expect(find('.dropdown-menu-user a.is-active').first(:xpath, '..')['data-user-id']).to eq(user2.id.to_s)
+ end
+ end
+ end
+
describe 'single assignee' do
before do
click_button 'Unassigned'
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 99ad8013023..96c24750250 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -57,6 +57,23 @@ feature 'Issue Sidebar', feature: true do
expect(page.find('.dropdown-menu-user-link.is-active')).to have_content(user.name)
end
end
+
+ it 'keeps your filtered term after filtering and dismissing the dropdown' do
+ find('.dropdown-input-field').native.send_keys user2.name
+
+ wait_for_requests
+
+ page.within '.dropdown-menu-user' do
+ expect(page).not_to have_content 'Unassigned'
+ click_link user2.name
+ end
+
+ find('.js-right-sidebar').click
+ find('.block.assignee .edit-link').click
+
+ expect(page.all('.dropdown-menu-user li').length).to eq(1)
+ expect(find('.dropdown-input-field').value).to eq(user2.name)
+ end
end
context 'as a allowed user' do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 6c09903a2f6..e75bf059218 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -38,9 +38,11 @@ feature 'issue move to another project' do
end
scenario 'moving issue to another project', js: true do
- find('#move_to_project_id', visible: false).set(new_project.id)
+ find('#issuable-move', visible: false).set(new_project.id)
click_button('Save changes')
+ wait_for_requests
+
expect(current_url).to include project_path(new_project)
expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
@@ -51,7 +53,7 @@ feature 'issue move to another project' do
scenario 'searching project dropdown', js: true do
new_project_search.team << [user, :reporter]
- page.within '.js-move-dropdown' do
+ page.within '.detail-page-description' do
first('.select2-choice').click
end
@@ -69,7 +71,7 @@ feature 'issue move to another project' do
background { another_project.team << [user, :guest] }
scenario 'browsing projects in projects select' do
- click_link 'Select project'
+ click_link 'Move to a different project'
page.within '.select2-results' do
expect(page).to have_content 'No project'
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index ec87a99b3ab..c77a5c68bc6 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -29,6 +29,19 @@ feature 'Edit Merge Request', feature: true do
expect(page).to have_content 'Someone edited the merge request the same time you did'
end
+ it 'allows to unselect "Remove source branch"', js: true do
+ merge_request.update(merge_params: { 'force_remove_source_branch' => '1' })
+ expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
+
+ visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ uncheck 'Remove source branch when merge request is accepted'
+
+ click_button 'Save changes'
+
+ expect(page).to have_unchecked_field 'remove-source-branch-input'
+ expect(page).to have_content 'Remove source branch'
+ end
+
it 'should preserve description textarea height', js: true do
long_description = %q(
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ac ornare ligula, ut tempus arcu. Etiam ultricies accumsan dolor vitae faucibus. Donec at elit lacus. Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. Aenean at pulvinar lacus. Ut viverra quam massa, molestie ornare tortor dignissim a. Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. Ut tincidunt est purus, ac vestibulum augue maximus in. Suspendisse vel erat et mi ultricies semper. Pellentesque volutpat pellentesque consequat.
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index e08721b4724..09f889d4dd6 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -7,7 +7,8 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
let(:merge_request) do
create(:merge_request_with_diffs, source_project: project,
author: user,
- title: 'Bug NS-04')
+ title: 'Bug NS-04',
+ merge_params: { force_remove_source_branch: '1' })
end
let(:pipeline) do
@@ -41,7 +42,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
click_button "Merge when pipeline succeeds"
expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds."
- expect(page).to have_content "The source branch will be removed."
+ expect(page).to have_content "The source branch will not be removed."
expect(page).to have_selector ".js-cancel-auto-merge"
visit_merge_request(merge_request) # Needed to refresh the page
expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i
@@ -82,7 +83,8 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
source_project: project,
title: 'Bug NS-04',
author: user,
- merge_user: user)
+ merge_user: user,
+ merge_params: { force_remove_source_branch: '1' })
end
before do
@@ -99,7 +101,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
click_link 'Merge when pipeline succeeds'
expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds."
- expect(page).to have_content "The source branch will be removed."
+ expect(page).to have_content "The source branch will not be removed."
expect(page).to have_link "Cancel automatic merge"
end
end
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index 51e7467c14c..3ceb91d951d 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -85,7 +85,7 @@ feature 'Mini Pipeline Graph', :js, :feature do
build_item.click
find('.build-page')
- expect(current_path).to eql(namespace_project_build_path(project.namespace, project, build))
+ expect(current_path).to eql(namespace_project_job_path(project.namespace, project, build))
end
it 'should show tooltip when hovered' do
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index be8b1423c20..4f3a5119915 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -202,4 +202,25 @@ describe 'Merge request', :feature, :js do
end
end
end
+
+ context 'user can merge into source project but cannot push to fork', js: true do
+ let(:fork_project) { create(:project, :public) }
+ let(:user2) { create(:user) }
+
+ before do
+ project.team << [user2, :master]
+ logout
+ login_as user2
+ merge_request.update(target_project: fork_project)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'user can merge into the source project' do
+ expect(page).to have_button('Merge', disabled: false)
+ end
+
+ it 'user cannot remove source branch' do
+ expect(page).to have_field('remove-source-branch-input', disabled: true)
+ end
+ end
end
diff --git a/spec/features/projects/artifacts/browse_spec.rb b/spec/features/projects/artifacts/browse_spec.rb
new file mode 100644
index 00000000000..68375956273
--- /dev/null
+++ b/spec/features/projects/artifacts/browse_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+feature 'Browse artifact', :js, feature: true do
+ let(:project) { create(:project, :public) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ def browse_path(path)
+ browse_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+ end
+
+ context 'when visiting old URL' do
+ let(:browse_url) do
+ browse_path('other_artifacts_0.1.2')
+ end
+
+ before do
+ visit browse_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(browse_url)
+ end
+ end
+end
diff --git a/spec/features/projects/artifacts/download_spec.rb b/spec/features/projects/artifacts/download_spec.rb
new file mode 100644
index 00000000000..dd9454840ee
--- /dev/null
+++ b/spec/features/projects/artifacts/download_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+feature 'Download artifact', :js, feature: true do
+ let(:project) { create(:project, :public) }
+ let(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project, sha: project.commit.sha, ref: 'master') }
+ let(:job) { create(:ci_build, :artifacts, :success, pipeline: pipeline) }
+
+ shared_examples 'downloading' do
+ it 'downloads the zip' do
+ expect(page.response_headers['Content-Disposition'])
+ .to eq(%Q{attachment; filename="#{job.artifacts_file.filename}"})
+
+ # Check the content does match, but don't print this as error message
+ expect(page.source.b == job.artifacts_file.file.read.b)
+ end
+ end
+
+ context 'when downloading' do
+ before do
+ visit download_url
+ end
+
+ context 'via job id' do
+ let(:download_url) do
+ download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ end
+
+ it_behaves_like 'downloading'
+ end
+
+ context 'via branch name and job name' do
+ let(:download_url) do
+ latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+ end
+
+ it_behaves_like 'downloading'
+ end
+ end
+
+ context 'when visiting old URL' do
+ before do
+ visit download_url.sub('/-/jobs', '/builds')
+ end
+
+ context 'via job id' do
+ let(:download_url) do
+ download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ end
+
+ it_behaves_like 'downloading'
+ end
+
+ context 'via branch name and job name' do
+ let(:download_url) do
+ latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+ end
+
+ it_behaves_like 'downloading'
+ end
+ end
+end
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index 25db908d917..25c4f3c87a2 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -6,7 +6,11 @@ feature 'Artifact file', :js, feature: true do
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
def visit_file(path)
- visit file_namespace_project_build_artifacts_path(project.namespace, project, build, path)
+ visit file_path(path)
+ end
+
+ def file_path(path)
+ file_namespace_project_job_artifacts_path(project.namespace, project, build, path)
end
context 'Text file' do
@@ -56,4 +60,18 @@ feature 'Artifact file', :js, feature: true do
end
end
end
+
+ context 'when visiting old URL' do
+ let(:file_url) do
+ file_path('other_artifacts_0.1.2/doc_sample.txt')
+ end
+
+ before do
+ visit file_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(file_url)
+ end
+ end
end
diff --git a/spec/features/projects/artifacts/raw_spec.rb b/spec/features/projects/artifacts/raw_spec.rb
new file mode 100644
index 00000000000..b589701729d
--- /dev/null
+++ b/spec/features/projects/artifacts/raw_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+feature 'Raw artifact', :js, feature: true do
+ let(:project) { create(:project, :public) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ def raw_path(path)
+ raw_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+ end
+
+ context 'when visiting old URL' do
+ let(:raw_url) do
+ raw_path('other_artifacts_0.1.2/doc_sample.txt')
+ end
+
+ before do
+ visit raw_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(raw_url)
+ end
+ end
+end
diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
index 2352329d58c..0c51fe72ca4 100644
--- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb
+++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
@@ -56,14 +56,8 @@ feature 'Developer views empty project instructions', feature: true do
end
def expect_instructions_for(protocol)
- url =
- case protocol
- when 'ssh'
- project.ssh_url_to_repo
- when 'http'
- project.http_url_to_repo(developer)
- end
-
- expect(page).to have_content("git clone #{url}")
+ msg = :"#{protocol.downcase}_url_to_repo"
+
+ expect(page).to have_content("git clone #{project.send(msg)}")
end
end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 86ce50c976f..18b608c863e 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -12,6 +12,7 @@ feature 'Environment', :feature do
feature 'environment details page' do
given!(:environment) { create(:environment, project: project) }
+ given!(:permissions) { }
given!(:deployment) { }
given!(:action) { }
@@ -62,20 +63,31 @@ feature 'Environment', :feature do
name: 'deploy to production')
end
- given(:role) { :master }
+ context 'when user has ability to trigger deployment' do
+ given(:permissions) do
+ create(:protected_branch, :developers_can_merge,
+ name: action.ref, project: project)
+ end
- scenario 'does show a play button' do
- expect(page).to have_link(action.name.humanize)
- end
+ it 'does show a play button' do
+ expect(page).to have_link(action.name.humanize)
+ end
+
+ it 'does allow to play manual action' do
+ expect(action).to be_manual
- scenario 'does allow to play manual action' do
- expect(action).to be_manual
+ expect { click_link(action.name.humanize) }
+ .not_to change { Ci::Pipeline.count }
- expect { click_link(action.name.humanize) }
- .not_to change { Ci::Pipeline.count }
+ expect(page).to have_content(action.name)
+ expect(action.reload).to be_pending
+ end
+ end
- expect(page).to have_content(action.name)
- expect(action.reload).to be_pending
+ context 'when user has no ability to trigger a deployment' do
+ it 'does not show a play button' do
+ expect(page).not_to have_link(action.name.humanize)
+ end
end
context 'with external_url' do
@@ -134,12 +146,23 @@ feature 'Environment', :feature do
on_stop: 'close_app')
end
- given(:role) { :master }
+ context 'when user has ability to stop environment' do
+ given(:permissions) do
+ create(:protected_branch, :developers_can_merge,
+ name: action.ref, project: project)
+ end
- scenario 'does allow to stop environment' do
- click_link('Stop')
+ it 'allows to stop environment' do
+ click_link('Stop')
- expect(page).to have_content('close_app')
+ expect(page).to have_content('close_app')
+ end
+ end
+
+ context 'when user has no ability to stop environment' do
+ it 'does not allow to stop environment' do
+ expect(page).to have_no_link('Stop')
+ end
end
context 'for reporter' do
@@ -150,12 +173,6 @@ feature 'Environment', :feature do
end
end
end
-
- context 'without stop action' do
- scenario 'does allow to stop environment' do
- click_link('Stop')
- end
- end
end
context 'when environment is stopped' do
diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb
index c969acc9140..4e5682c8636 100644
--- a/spec/features/projects/group_links_spec.rb
+++ b/spec/features/projects/group_links_spec.rb
@@ -40,7 +40,7 @@ feature 'Project group links', :feature, :js do
another_group.add_master(master)
end
- it 'does not show ancestors' do
+ it 'does not show ancestors', :nested_groups do
visit namespace_project_settings_members_path(project.namespace, project)
click_link 'Search for a group'
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/jobs_spec.rb
index ab10434e10c..0eda46649db 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'tempfile'
-feature 'Builds', :feature do
+feature 'Jobs', :feature do
let(:user) { create(:user) }
let(:user_access_level) { :developer }
let(:project) { create(:project) }
@@ -19,12 +19,12 @@ feature 'Builds', :feature do
login_as(user)
end
- describe "GET /:project/builds" do
+ describe "GET /:project/jobs" do
let!(:build) { create(:ci_build, pipeline: pipeline) }
context "Pending scope" do
before do
- visit namespace_project_builds_path(project.namespace, project, scope: :pending)
+ visit namespace_project_jobs_path(project.namespace, project, scope: :pending)
end
it "shows Pending tab jobs" do
@@ -39,7 +39,7 @@ feature 'Builds', :feature do
context "Running scope" do
before do
build.run!
- visit namespace_project_builds_path(project.namespace, project, scope: :running)
+ visit namespace_project_jobs_path(project.namespace, project, scope: :running)
end
it "shows Running tab jobs" do
@@ -54,7 +54,7 @@ feature 'Builds', :feature do
context "Finished scope" do
before do
build.run!
- visit namespace_project_builds_path(project.namespace, project, scope: :finished)
+ visit namespace_project_jobs_path(project.namespace, project, scope: :finished)
end
it "shows Finished tab jobs" do
@@ -67,7 +67,7 @@ feature 'Builds', :feature do
context "All jobs" do
before do
project.builds.running_or_pending.each(&:success)
- visit namespace_project_builds_path(project.namespace, project)
+ visit namespace_project_jobs_path(project.namespace, project)
end
it "shows All tab jobs" do
@@ -78,12 +78,26 @@ feature 'Builds', :feature do
expect(page).not_to have_link 'Cancel running'
end
end
+
+ context "when visiting old URL" do
+ let(:jobs_url) do
+ namespace_project_jobs_path(project.namespace, project)
+ end
+
+ before do
+ visit jobs_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(jobs_url)
+ end
+ end
end
- describe "POST /:project/builds/:id/cancel_all" do
+ describe "POST /:project/jobs/:id/cancel_all" do
before do
build.run!
- visit namespace_project_builds_path(project.namespace, project)
+ visit namespace_project_jobs_path(project.namespace, project)
click_link "Cancel running"
end
@@ -97,10 +111,10 @@ feature 'Builds', :feature do
end
end
- describe "GET /:project/builds/:id" do
+ describe "GET /:project/jobs/:id" do
context "Job from project" do
before do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
it 'shows commit`s data' do
@@ -117,7 +131,7 @@ feature 'Builds', :feature do
context "Job from other project" do
before do
- visit namespace_project_build_path(project.namespace, project, build2)
+ visit namespace_project_job_path(project.namespace, project, build2)
end
it { expect(page.status_code).to eq(404) }
@@ -126,7 +140,7 @@ feature 'Builds', :feature do
context "Download artifacts" do
before do
build.update_attributes(artifacts_file: artifacts_file)
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
it 'has button to download artifacts' do
@@ -139,7 +153,7 @@ feature 'Builds', :feature do
build.update_attributes(artifacts_file: artifacts_file,
artifacts_expire_at: expire_at)
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
context 'no expire date defined' do
@@ -183,14 +197,29 @@ feature 'Builds', :feature do
end
end
+ context "when visiting old URL" do
+ let(:job_url) do
+ namespace_project_job_path(project.namespace, project, build)
+ end
+
+ before do
+ visit job_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(job_url)
+ end
+ end
+
feature 'Raw trace' do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+
+ visit namespace_project_job_path(project.namespace, project, build)
end
it do
- expect(page).to have_link 'Raw'
+ expect(page).to have_css('.js-raw-link')
end
end
@@ -198,7 +227,7 @@ feature 'Builds', :feature do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
context 'when job has an initial trace' do
@@ -222,7 +251,7 @@ feature 'Builds', :feature do
end
before do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
it 'shows variable key and value after click', js: true do
@@ -247,17 +276,17 @@ feature 'Builds', :feature do
let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
it 'shows a link for the job' do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
expect(page).to have_link environment.name
end
end
- context 'job is complete and not successfull' do
+ context 'job is complete and not successful' do
let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
it 'shows a link for the job' do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
expect(page).to have_link environment.name
end
@@ -268,7 +297,7 @@ feature 'Builds', :feature do
let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
it 'shows a link to latest deployment' do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
expect(page).to have_link('latest deployment')
end
@@ -276,11 +305,11 @@ feature 'Builds', :feature do
end
end
- describe "POST /:project/builds/:id/cancel" do
+ describe "POST /:project/jobs/:id/cancel" do
context "Job from project" do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
click_link "Cancel"
end
@@ -294,19 +323,19 @@ feature 'Builds', :feature do
context "Job from other project" do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
- page.driver.post(cancel_namespace_project_build_path(project.namespace, project, build2))
+ visit namespace_project_job_path(project.namespace, project, build)
+ page.driver.post(cancel_namespace_project_job_path(project.namespace, project, build2))
end
it { expect(page.status_code).to eq(404) }
end
end
- describe "POST /:project/builds/:id/retry" do
+ describe "POST /:project/jobs/:id/retry" do
context "Job from project" do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
click_link 'Cancel'
page.within('.build-header') do
click_link 'Retry job'
@@ -322,18 +351,18 @@ feature 'Builds', :feature do
end
end
- context "Build from other project" do
+ context "Job from other project" do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
click_link 'Cancel'
- page.driver.post(retry_namespace_project_build_path(project.namespace, project, build2))
+ page.driver.post(retry_namespace_project_job_path(project.namespace, project, build2))
end
it { expect(page).to have_http_status(404) }
end
- context "Build that current user is not allowed to retry" do
+ context "Job that current user is not allowed to retry" do
before do
build.run!
build.cancel!
@@ -341,7 +370,7 @@ feature 'Builds', :feature do
logout_direct
login_with(create(:user))
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
it 'does not show the Retry button' do
@@ -352,31 +381,31 @@ feature 'Builds', :feature do
end
end
- describe "GET /:project/builds/:id/download" do
+ describe "GET /:project/jobs/:id/download" do
before do
build.update_attributes(artifacts_file: artifacts_file)
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
click_link 'Download'
end
context "Build from other project" do
before do
build2.update_attributes(artifacts_file: artifacts_file)
- visit download_namespace_project_build_artifacts_path(project.namespace, project, build2)
+ visit download_namespace_project_job_artifacts_path(project.namespace, project, build2)
end
it { expect(page.status_code).to eq(404) }
end
end
- describe 'GET /:project/builds/:id/raw' do
+ describe 'GET /:project/jobs/:id/raw', :js do
context 'access source' do
- context 'build from project' do
+ context 'job from project' do
before do
- Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
- page.within('.js-build-sidebar') { click_link 'Raw' }
+ visit namespace_project_job_path(project.namespace, project, build)
+ find('.js-raw-link-controller').click()
end
it 'sends the right headers' do
@@ -386,11 +415,11 @@ feature 'Builds', :feature do
end
end
- context 'build from other project' do
+ context 'job from other project' do
before do
- Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
build2.run!
- visit raw_namespace_project_build_path(project.namespace, project, build2)
+ visit raw_namespace_project_job_path(project.namespace, project, build2)
end
it 'sends the right headers' do
@@ -403,23 +432,23 @@ feature 'Builds', :feature do
let(:existing_file) { Tempfile.new('existing-trace-file').path }
before do
- Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
build.run!
allow_any_instance_of(Gitlab::Ci::Trace).to receive(:paths)
.and_return(paths)
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
- context 'when build has trace in file' do
+ context 'when build has trace in file', :js do
let(:paths) do
[existing_file]
end
before do
- page.within('.js-build-sidebar') { click_link 'Raw' }
+ find('.js-raw-link-controller').click()
end
it 'sends the right headers' do
@@ -429,46 +458,60 @@ feature 'Builds', :feature do
end
end
- context 'when build has trace in DB' do
+ context 'when job has trace in DB' do
let(:paths) { [] }
it 'sends the right headers' do
- expect(page.status_code).not_to have_link('Raw')
+ expect(page.status_code).not_to have_selector('.js-raw-link-controller')
end
end
end
+
+ context "when visiting old URL" do
+ let(:raw_job_url) do
+ raw_namespace_project_job_path(project.namespace, project, build)
+ end
+
+ before do
+ visit raw_job_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(raw_job_url)
+ end
+ end
end
- describe "GET /:project/builds/:id/trace.json" do
- context "Build from project" do
+ describe "GET /:project/jobs/:id/trace.json" do
+ context "Job from project" do
before do
- visit trace_namespace_project_build_path(project.namespace, project, build, format: :json)
+ visit trace_namespace_project_job_path(project.namespace, project, build, format: :json)
end
it { expect(page.status_code).to eq(200) }
end
- context "Build from other project" do
+ context "Job from other project" do
before do
- visit trace_namespace_project_build_path(project.namespace, project, build2, format: :json)
+ visit trace_namespace_project_job_path(project.namespace, project, build2, format: :json)
end
it { expect(page.status_code).to eq(404) }
end
end
- describe "GET /:project/builds/:id/status" do
- context "Build from project" do
+ describe "GET /:project/jobs/:id/status" do
+ context "Job from project" do
before do
- visit status_namespace_project_build_path(project.namespace, project, build)
+ visit status_namespace_project_job_path(project.namespace, project, build)
end
it { expect(page.status_code).to eq(200) }
end
- context "Build from other project" do
+ context "Job from other project" do
before do
- visit status_namespace_project_build_path(project.namespace, project, build2)
+ visit status_namespace_project_job_path(project.namespace, project, build2)
end
it { expect(page.status_code).to eq(404) }
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index b7ae5f0b925..d428f6fcf22 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -3,10 +3,9 @@ require 'spec_helper'
feature 'Projects > Members > Sorting', feature: true do
let(:master) { create(:user, name: 'John Doe') }
let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) }
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, namespace: master.namespace, creator: master) }
background do
- create(:project_member, :master, user: master, project: project, created_at: 5.days.ago)
create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago)
login_as(master)
@@ -39,16 +38,16 @@ feature 'Projects > Members > Sorting', feature: true do
scenario 'sorts by last joined' do
visit_members_list(sort: :last_joined)
- expect(first_member).to include(developer.name)
- expect(second_member).to include(master.name)
+ expect(first_member).to include(master.name)
+ expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
end
scenario 'sorts by oldest joined' do
visit_members_list(sort: :oldest_joined)
- expect(first_member).to include(master.name)
- expect(second_member).to include(developer.name)
+ expect(first_member).to include(developer.name)
+ expect(second_member).to include(master.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
end
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 1bf8f710b9f..ec48a4bd726 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -2,11 +2,10 @@ require 'spec_helper'
feature 'Projects > Members > User requests access', feature: true do
let(:user) { create(:user) }
- let(:master) { create(:user) }
let(:project) { create(:project, :public, :access_requestable) }
+ let(:master) { project.owner }
background do
- project.team << [master, :master]
login_as(user)
visit namespace_project_path(project.namespace, project)
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index f40e1bc4930..317949d6b56 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -65,6 +65,17 @@ feature 'Pipeline Schedules', :feature do
expect(page).not_to have_content('pipeline schedule')
end
end
+
+ context 'when ref is nil' do
+ before do
+ pipeline_schedule.update_attribute(:ref, nil)
+ visit_pipelines_schedules
+ end
+
+ it 'shows a list of the pipeline schedules with empty ref column' do
+ expect(first('.branch-name-cell').text).to eq('')
+ end
+ end
end
describe 'POST /projects/pipeline_schedules/new', js: true do
@@ -108,6 +119,19 @@ feature 'Pipeline Schedules', :feature do
expect(page).to have_content('my brand new description')
end
+
+ context 'when ref is nil' do
+ before do
+ pipeline_schedule.update_attribute(:ref, nil)
+ edit_pipeline_schedule
+ end
+
+ it 'shows the pipeline schedule with default ref' do
+ page.within('.git-revision-dropdown-toggle') do
+ expect(first('.dropdown-toggle-text').text).to eq('master')
+ end
+ end
+ end
end
def visit_new_pipeline_schedule
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index a97a92aa64f..05c2bf350f1 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -442,6 +442,8 @@ describe 'Pipelines', :feature, :js do
it 'creates a new pipeline' do
expect { click_on 'Create pipeline' }
.to change { Ci::Pipeline.count }.by(1)
+
+ expect(Ci::Pipeline.last).to be_web
end
end
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index cf21b208f65..e88907b8016 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Subgroup Issuables', :feature, :js do
+describe 'Subgroup Issuables', :feature, :js, :nested_groups do
let!(:group) { create(:group, name: 'group') }
let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
let!(:project) { create(:empty_project, namespace: subgroup, name: 'project') }
diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
index 6825b95c8aa..95826e7e5be 100644
--- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
@@ -21,6 +21,6 @@ describe 'Projects > Wiki > User views Git access wiki page', :feature do
click_link 'Clone repository'
expect(page).to have_text("Clone repository #{project.wiki.path_with_namespace}")
- expect(page).to have_text(project.wiki.http_url_to_repo(user))
+ expect(page).to have_text(project.wiki.http_url_to_repo)
end
end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 78a76d9c112..2a2655bbdb5 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -334,7 +334,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_builds_path(project.namespace, project) }
+ subject { namespace_project_jobs_path(project.namespace, project) }
context "when allowed for public and internal" do
before { project.update(public_builds: true) }
@@ -368,7 +368,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_build_path(project.namespace, project, build.id) }
+ subject { namespace_project_job_path(project.namespace, project, build.id) }
context "when allowed for public and internal" do
before { project.update(public_builds: true) }
@@ -402,7 +402,7 @@ describe "Internal Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_build_path(project.namespace, project, build.id) }
+ subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
context 'when allowed for public and internal' do
before do
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index a66f6e09055..b676c236758 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -330,7 +330,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_builds_path(project.namespace, project) }
+ subject { namespace_project_jobs_path(project.namespace, project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -358,7 +358,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_build_path(project.namespace, project, build.id) }
+ subject { namespace_project_job_path(project.namespace, project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -391,7 +391,7 @@ describe "Private Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_build_path(project.namespace, project, build.id) }
+ subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 5cd575500c3..35d5163941e 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -154,7 +154,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_builds_path(project.namespace, project) }
+ subject { namespace_project_jobs_path(project.namespace, project) }
context "when allowed for public" do
before { project.update(public_builds: true) }
@@ -188,7 +188,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_build_path(project.namespace, project, build.id) }
+ subject { namespace_project_job_path(project.namespace, project, build.id) }
context "when allowed for public" do
before { project.update(public_builds: true) }
@@ -222,7 +222,7 @@ describe "Public Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_build_path(project.namespace, project, build.id) }
+ subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
context 'when allowed for public' do
before do
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index b762756f9ce..db3fcc23475 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -18,7 +18,7 @@ describe GroupMembersFinder, '#execute' do
expect(result.to_a).to eq([member3, member2, member1])
end
- it 'returns members for nested group' do
+ it 'returns members for nested group', :nested_groups do
group.add_master(user2)
nested_group.request_access(user4)
member1 = group.add_master(user1)
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index cf691cf684b..300ba8422e8 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -9,7 +9,7 @@ describe MembersFinder, '#execute' do
let(:user3) { create(:user) }
let(:user4) { create(:user) }
- it 'returns members for project and parent groups' do
+ it 'returns members for project and parent groups', :nested_groups do
nested_group.request_access(user1)
member1 = group.add_master(user2)
member2 = nested_group.add_master(user3)
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 148adcffe3b..03d98459e8c 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -137,6 +137,13 @@ describe ProjectsFinder do
it { is_expected.to eq([public_project]) }
end
+ describe 'filter by owned' do
+ let(:params) { { owned: true } }
+ let!(:owned_project) { create(:empty_project, :private, namespace: current_user.namespace) }
+
+ it { is_expected.to eq([owned_project]) }
+ end
+
describe 'filter by non_public' do
let(:params) { { non_public: true } }
before do
@@ -146,13 +153,19 @@ describe ProjectsFinder do
it { is_expected.to eq([private_project]) }
end
- describe 'filter by viewable_starred_projects' do
+ describe 'filter by starred' do
let(:params) { { starred: true } }
before do
current_user.toggle_star(public_project)
end
it { is_expected.to eq([public_project]) }
+
+ it 'returns only projects the user has access to' do
+ current_user.toggle_star(private_project)
+
+ is_expected.to eq([public_project])
+ end
end
describe 'sorting' do
diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json
index 4afbb87453e..b6a59a6cc47 100644
--- a/spec/fixtures/api/schemas/entities/merge_request.json
+++ b/spec/fixtures/api/schemas/entities/merge_request.json
@@ -92,7 +92,8 @@
"diverged_commits_count": { "type": "integer" },
"commit_change_content_path": { "type": "string" },
"remove_wip_path": { "type": "string" },
- "commits_count": { "type": "integer" }
+ "commits_count": { "type": "integer" },
+ "remove_source_branch": { "type": ["boolean", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/pipeline_schedule.json b/spec/fixtures/api/schemas/pipeline_schedule.json
new file mode 100644
index 00000000000..f6346bd0fb6
--- /dev/null
+++ b/spec/fixtures/api/schemas/pipeline_schedule.json
@@ -0,0 +1,41 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "description": { "type": "string" },
+ "ref": { "type": "string" },
+ "cron": { "type": "string" },
+ "cron_timezone": { "type": "string" },
+ "next_run_at": { "type": "date" },
+ "active": { "type": "boolean" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "last_pipeline": {
+ "type": ["object", "null"],
+ "properties": {
+ "id": { "type": "integer" },
+ "sha": { "type": "string" },
+ "ref": { "type": "string" },
+ "status": { "type": "string" }
+ },
+ "additionalProperties": false
+ },
+ "owner": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "id", "description", "ref", "cron", "cron_timezone", "next_run_at",
+ "active", "created_at", "updated_at", "owner"
+ ],
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/pipeline_schedules.json b/spec/fixtures/api/schemas/pipeline_schedules.json
new file mode 100644
index 00000000000..173a28d2505
--- /dev/null
+++ b/spec/fixtures/api/schemas/pipeline_schedules.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "pipeline_schedule.json" }
+}
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 41b5df12522..bd3a3d24b84 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -118,8 +118,8 @@ describe BlobHelper do
Class.new(BlobViewer::Base) do
include BlobViewer::ServerSide
- self.overridable_max_size = 1.megabyte
- self.max_size = 5.megabytes
+ self.collapse_limit = 1.megabyte
+ self.size_limit = 5.megabytes
self.type = :rich
end
end
@@ -129,7 +129,7 @@ describe BlobHelper do
describe '#blob_render_error_reason' do
context 'for error :too_large' do
- context 'when the blob size is larger than the absolute max size' do
+ context 'when the blob size is larger than the absolute size limit' do
let(:blob) { fake_blob(size: 10.megabytes) }
it 'returns an error message' do
@@ -137,7 +137,7 @@ describe BlobHelper do
end
end
- context 'when the blob size is larger than the max size' do
+ context 'when the blob size is larger than the size limit' do
let(:blob) { fake_blob(size: 2.megabytes) }
it 'returns an error message' do
@@ -168,21 +168,19 @@ describe BlobHelper do
controller.params[:id] = File.join('master', blob.path)
end
- context 'for error :too_large' do
- context 'when the max size can be overridden' do
- let(:blob) { fake_blob(size: 2.megabytes) }
+ context 'for error :collapsed' do
+ let(:blob) { fake_blob(size: 2.megabytes) }
- it 'includes a "load it anyway" link' do
- expect(helper.blob_render_error_options(viewer)).to include(/load it anyway/)
- end
+ it 'includes a "load it anyway" link' do
+ expect(helper.blob_render_error_options(viewer)).to include(/load it anyway/)
end
+ end
- context 'when the max size cannot be overridden' do
- let(:blob) { fake_blob(size: 10.megabytes) }
+ context 'for error :too_large' do
+ let(:blob) { fake_blob(size: 10.megabytes) }
- it 'does not include a "load it anyway" link' do
- expect(helper.blob_render_error_options(viewer)).not_to include(/load it anyway/)
- end
+ it 'does not include a "load it anyway" link' do
+ expect(helper.blob_render_error_options(viewer)).not_to include(/load it anyway/)
end
context 'when the viewer is rich' do
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index dd6566d25bb..84341a51f2d 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -33,17 +33,17 @@ describe DiffHelper do
describe 'diff_options' do
it 'returns no collapse false' do
- expect(diff_options).to include(no_collapse: false)
+ expect(diff_options).to include(expanded: false)
end
- it 'returns no collapse true if expand_all_diffs' do
- allow(controller).to receive(:params) { { expand_all_diffs: true } }
- expect(diff_options).to include(no_collapse: true)
+ it 'returns no collapse true if expanded' do
+ allow(controller).to receive(:params) { { expanded: true } }
+ expect(diff_options).to include(expanded: true)
end
it 'returns no collapse true if action name diff_for_path' do
allow(controller).to receive(:action_name) { 'diff_for_path' }
- expect(diff_options).to include(no_collapse: true)
+ expect(diff_options).to include(expanded: true)
end
it 'returns paths if action name diff_for_path and param old path' do
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index 18935be95c9..b05ae5c2232 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -115,6 +115,11 @@ describe SubmoduleHelper do
expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
+ it 'handles urls with trailing whitespace' do
+ stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git ')
+ expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ end
+
it 'returns original with non-standard url' do
stub_url('http://gitlab.com/another/gitlab-org/gitlab-ce.git')
expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js
index 8ec96bdb583..461908f3fde 100644
--- a/spec/javascripts/build_spec.js
+++ b/spec/javascripts/build_spec.js
@@ -8,13 +8,12 @@ import '~/breakpoints';
import 'vendor/jquery.nicescroll';
describe('Build', () => {
- const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`;
+ const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`;
preloadFixtures('builds/build-with-artifacts.html.raw');
beforeEach(() => {
loadFixtures('builds/build-with-artifacts.html.raw');
- spyOn($, 'ajax');
});
describe('class constructor', () => {
@@ -33,7 +32,6 @@ describe('Build', () => {
it('copies build options', function () {
expect(this.build.pageUrl).toBe(BUILD_URL);
- expect(this.build.buildUrl).toBe(`${BUILD_URL}.json`);
expect(this.build.buildStatus).toBe('success');
expect(this.build.buildStage).toBe('test');
expect(this.build.state).toBe('');
@@ -65,27 +63,14 @@ describe('Build', () => {
});
describe('running build', () => {
- beforeEach(function () {
- this.build = new Build();
- });
-
it('updates the build trace on an interval', function () {
+ const deferred1 = $.Deferred();
+ const deferred2 = $.Deferred();
+ const deferred3 = $.Deferred();
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
spyOn(gl.utils, 'visitUrl');
- jasmine.clock().tick(4001);
-
- expect($.ajax.calls.count()).toBe(1);
-
- // We have to do it this way to prevent Webpack to fail to compile
- // when destructuring assignments and reusing
- // the same variables names inside the same scope
- let args = $.ajax.calls.argsFor(0)[0];
-
- expect(args.url).toBe(`${BUILD_URL}/trace.json`);
- expect(args.dataType).toBe('json');
- expect(args.success).toEqual(jasmine.any(Function));
-
- args.success.call($, {
+ deferred1.resolve({
html: '<span>Update<span>',
status: 'running',
state: 'newstate',
@@ -93,20 +78,9 @@ describe('Build', () => {
complete: false,
});
- expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
- expect(this.build.state).toBe('newstate');
-
- jasmine.clock().tick(4001);
-
- expect($.ajax.calls.count()).toBe(3);
-
- args = $.ajax.calls.argsFor(2)[0];
- expect(args.url).toBe(`${BUILD_URL}/trace.json`);
- expect(args.dataType).toBe('json');
- expect(args.data.state).toBe('newstate');
- expect(args.success).toEqual(jasmine.any(Function));
+ deferred2.resolve();
- args.success.call($, {
+ deferred3.resolve({
html: '<span>More</span>',
status: 'running',
state: 'finalstate',
@@ -114,150 +88,222 @@ describe('Build', () => {
complete: true,
});
+ this.build = new Build();
+
+ expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+ expect(this.build.state).toBe('newstate');
+
+ jasmine.clock().tick(4001);
+
expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
expect(this.build.state).toBe('finalstate');
});
it('replaces the entire build trace', () => {
+ const deferred1 = $.Deferred();
+ const deferred2 = $.Deferred();
+ const deferred3 = $.Deferred();
+
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
+
spyOn(gl.utils, 'visitUrl');
- jasmine.clock().tick(4001);
- let args = $.ajax.calls.argsFor(0)[0];
- args.success.call($, {
- html: '<span>Update</span>',
+ deferred1.resolve({
+ html: '<span>Update<span>',
status: 'running',
append: false,
complete: false,
});
- expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+ deferred2.resolve();
- jasmine.clock().tick(4001);
- args = $.ajax.calls.argsFor(2)[0];
- args.success.call($, {
+ deferred3.resolve({
html: '<span>Different</span>',
status: 'running',
append: false,
});
+ this.build = new Build();
+
+ expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+
+ jasmine.clock().tick(4001);
+
expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
});
it('reloads the page when the build is done', () => {
spyOn(gl.utils, 'visitUrl');
+ const deferred = $.Deferred();
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
- success.call($, {
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
html: '<span>Final</span>',
status: 'passed',
append: true,
complete: true,
});
+ this.build = new Build();
+
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL);
});
+ });
- describe('truncated information', () => {
- describe('when size is less than total', () => {
- it('shows information about truncated log', () => {
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
-
- success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- });
-
- expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
+ describe('truncated information', () => {
+ describe('when size is less than total', () => {
+ it('shows information about truncated log', () => {
+ spyOn(gl.utils, 'visitUrl');
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
});
- it('shows the size in KiB', () => {
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
- const size = 50;
-
- success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${bytesToKiB(size)}`);
+ this.build = new Build();
+
+ expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
+ });
+
+ it('shows the size in KiB', () => {
+ const size = 50;
+ spyOn(gl.utils, 'visitUrl');
+ const deferred = $.Deferred();
+
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size,
+ total: 100,
});
- it('shows incremented size', () => {
- jasmine.clock().tick(4001);
- let args = $.ajax.calls.argsFor(0)[0];
- args.success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${bytesToKiB(50)}`);
-
- jasmine.clock().tick(4001);
- args = $.ajax.calls.argsFor(2)[0];
- args.success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: true,
- size: 10,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${bytesToKiB(60)}`);
+ this.build = new Build();
+
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${bytesToKiB(size)}`);
+ });
+
+ it('shows incremented size', () => {
+ const deferred1 = $.Deferred();
+ const deferred2 = $.Deferred();
+ const deferred3 = $.Deferred();
+
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
+
+ spyOn(gl.utils, 'visitUrl');
+
+ deferred1.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
});
- it('renders the raw link', () => {
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
-
- success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-raw-link').textContent.trim(),
- ).toContain('Complete Raw');
+ deferred2.resolve();
+
+ this.build = new Build();
+
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${bytesToKiB(50)}`);
+
+ jasmine.clock().tick(4001);
+
+ deferred3.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: true,
+ size: 10,
+ total: 100,
});
+
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${bytesToKiB(60)}`);
});
- describe('when size is equal than total', () => {
- it('does not show the trunctated information', () => {
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
+ it('renders the raw link', () => {
+ const deferred = $.Deferred();
+ spyOn(gl.utils, 'visitUrl');
+
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ });
- success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 100,
- total: 100,
- });
+ this.build = new Build();
- expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
+ expect(
+ document.querySelector('.js-raw-link').textContent.trim(),
+ ).toContain('Complete Raw');
+ });
+ });
+
+ describe('when size is equal than total', () => {
+ it('does not show the trunctated information', () => {
+ const deferred = $.Deferred();
+ spyOn(gl.utils, 'visitUrl');
+
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 100,
+ total: 100,
});
+
+ this.build = new Build();
+
+ expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
+ });
+ });
+ });
+
+ describe('output trace', () => {
+ beforeEach(() => {
+ const deferred = $.Deferred();
+ spyOn(gl.utils, 'visitUrl');
+
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
});
+
+ this.build = new Build();
+ });
+
+ it('should render trace controls', () => {
+ const controllers = document.querySelector('.controllers');
+
+ expect(controllers.querySelector('.js-raw-link-controller')).toBeDefined();
+ expect(controllers.querySelector('.js-erase-link')).toBeDefined();
+ expect(controllers.querySelector('.js-scroll-up')).toBeDefined();
+ expect(controllers.querySelector('.js-scroll-down')).toBeDefined();
+ });
+
+ it('should render received output', () => {
+ expect(
+ document.querySelector('.js-build-output').innerHTML,
+ ).toEqual('<span>Update</span>');
});
});
});
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 8688332782d..6e59ee96c6b 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -57,6 +57,7 @@ describe('Filtered Search Manager', () => {
input = document.querySelector('.filtered-search');
tokensContainer = document.querySelector('.tokens-container');
manager = new gl.FilteredSearchManager();
+ manager.setup();
});
afterEach(() => {
@@ -72,6 +73,7 @@ describe('Filtered Search Manager', () => {
spyOn(recentSearchesStoreSrc, 'default');
filteredSearchManager = new gl.FilteredSearchManager();
+ filteredSearchManager.setup();
return filteredSearchManager;
});
@@ -89,6 +91,7 @@ describe('Filtered Search Manager', () => {
spyOn(window, 'Flash');
filteredSearchManager = new gl.FilteredSearchManager();
+ filteredSearchManager.setup();
expect(window.Flash).not.toHaveBeenCalled();
});
diff --git a/spec/javascripts/fixtures/builds.rb b/spec/javascripts/fixtures/jobs.rb
index 320de791b08..dc7dde1138c 100644
--- a/spec/javascripts/fixtures/builds.rb
+++ b/spec/javascripts/fixtures/jobs.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::BuildsController, '(JavaScript fixtures)', type: :controller do
+describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb
index 1ce622fc836..17533443d76 100644
--- a/spec/javascripts/fixtures/raw.rb
+++ b/spec/javascripts/fixtures/raw.rb
@@ -21,4 +21,10 @@ describe 'Raw files', '(JavaScript fixtures)', type: :controller do
store_frontend_fixture(blob.data, example.description)
end
+
+ it 'blob/notebook/math.json' do |example|
+ blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb')
+
+ store_frontend_fixture(blob.data, example.description)
+ end
end
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index ee456869c53..0030a953119 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import '~/render_math';
import '~/render_gfm';
import issuableApp from '~/issue_show/components/app.vue';
+import eventHub from '~/issue_show/event_hub';
import issueShowData from '../mock_data';
const issueShowInterceptor = data => (request, next) => {
@@ -22,14 +23,25 @@ describe('Issuable output', () => {
const IssuableDescriptionComponent = Vue.extend(issuableApp);
Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest));
+ spyOn(eventHub, '$emit');
+
vm = new IssuableDescriptionComponent({
propsData: {
canUpdate: true,
+ canDestroy: true,
+ canMove: true,
endpoint: '/gitlab-org/gitlab-shell/issues/9/realtime_changes',
issuableRef: '#1',
- initialTitle: '',
+ initialTitleHtml: '',
+ initialTitleText: '',
initialDescriptionHtml: '',
initialDescriptionText: '',
+ markdownPreviewUrl: '/',
+ markdownDocs: '/',
+ projectsAutocompleteUrl: '/',
+ isConfidential: false,
+ projectNamespace: '/',
+ projectPath: '/',
},
}).$mount();
});
@@ -57,4 +69,296 @@ describe('Issuable output', () => {
});
});
});
+
+ it('shows actions if permissions are correct', (done) => {
+ vm.showForm = true;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('does not show actions if permissions are incorrect', (done) => {
+ vm.showForm = true;
+ vm.canUpdate = false;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn'),
+ ).toBeNull();
+
+ done();
+ });
+ });
+
+ it('does not update formState if form is already open', (done) => {
+ vm.openForm();
+
+ vm.state.titleText = 'testing 123';
+
+ vm.openForm();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.store.formState.title,
+ ).not.toBe('testing 123');
+
+ done();
+ });
+ });
+
+ describe('updateIssuable', () => {
+ it('fetches new data after update', (done) => {
+ spyOn(vm.service, 'getData');
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ confidential: false,
+ web_url: location.pathname,
+ };
+ },
+ });
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ vm.service.getData,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('reloads the page if the confidential status has changed', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ confidential: true,
+ web_url: location.pathname,
+ };
+ },
+ });
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ gl.utils.visitUrl,
+ ).toHaveBeenCalledWith(location.pathname);
+
+ done();
+ });
+ });
+
+ it('correctly updates issuable data', (done) => {
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve();
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ vm.service.updateIssuable,
+ ).toHaveBeenCalledWith(vm.formState);
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+
+ done();
+ });
+ });
+
+ it('does not redirect if issue has not moved', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ web_url: location.pathname,
+ confidential: vm.isConfidential,
+ };
+ },
+ });
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ gl.utils.visitUrl,
+ ).not.toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('redirects if issue is moved', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ web_url: '/testing-issue-move',
+ confidential: vm.isConfidential,
+ };
+ },
+ });
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ gl.utils.visitUrl,
+ ).toHaveBeenCalledWith('/testing-issue-move');
+
+ done();
+ });
+ });
+
+ it('does not update issuable if project move confirm is false', (done) => {
+ spyOn(window, 'confirm').and.returnValue(false);
+ spyOn(vm.service, 'updateIssuable');
+
+ vm.store.formState.move_to_project_id = 1;
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ vm.service.updateIssuable,
+ ).not.toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('closes form on error', (done) => {
+ spyOn(window, 'Flash').and.callThrough();
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve, reject) => {
+ reject();
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+ expect(
+ window.Flash,
+ ).toHaveBeenCalledWith('Error updating issue');
+
+ done();
+ });
+ });
+ });
+
+ describe('deleteIssuable', () => {
+ it('changes URL when deleted', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return { web_url: '/test' };
+ },
+ });
+ }));
+
+ vm.deleteIssuable();
+
+ setTimeout(() => {
+ expect(
+ gl.utils.visitUrl,
+ ).toHaveBeenCalledWith('/test');
+
+ done();
+ });
+ });
+
+ it('stops polling when deleting', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.poll, 'stop');
+ spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return { web_url: '/test' };
+ },
+ });
+ }));
+
+ vm.deleteIssuable();
+
+ setTimeout(() => {
+ expect(
+ vm.poll.stop,
+ ).toHaveBeenCalledWith();
+
+ done();
+ });
+ });
+
+ it('closes form on error', (done) => {
+ spyOn(window, 'Flash').and.callThrough();
+ spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve, reject) => {
+ reject();
+ }));
+
+ vm.deleteIssuable();
+
+ setTimeout(() => {
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+ expect(
+ window.Flash,
+ ).toHaveBeenCalledWith('Error deleting issue');
+
+ done();
+ });
+ });
+ });
+
+ describe('open form', () => {
+ it('shows locked warning if form is open & data is different', (done) => {
+ Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest));
+
+ Vue.nextTick()
+ .then(() => new Promise((resolve) => {
+ setTimeout(resolve);
+ }))
+ .then(() => {
+ vm.openForm();
+
+ Vue.http.interceptors.push(issueShowInterceptor(issueShowData.secondRequest));
+
+ return new Promise((resolve) => {
+ setTimeout(resolve);
+ });
+ })
+ .then(() => {
+ expect(
+ vm.formState.lockedWarningVisible,
+ ).toBeTruthy();
+
+ expect(
+ vm.$el.querySelector('.alert'),
+ ).not.toBeNull();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/issue_show/components/edit_actions_spec.js b/spec/javascripts/issue_show/components/edit_actions_spec.js
new file mode 100644
index 00000000000..f6625b748b6
--- /dev/null
+++ b/spec/javascripts/issue_show/components/edit_actions_spec.js
@@ -0,0 +1,147 @@
+import Vue from 'vue';
+import editActions from '~/issue_show/components/edit_actions.vue';
+import eventHub from '~/issue_show/event_hub';
+import Store from '~/issue_show/stores';
+
+describe('Edit Actions components', () => {
+ let vm;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(editActions);
+ const store = new Store({
+ titleHtml: '',
+ descriptionHtml: '',
+ issuableRef: '',
+ });
+ store.formState.title = 'test';
+
+ spyOn(eventHub, '$emit');
+
+ vm = new Component({
+ propsData: {
+ canDestroy: true,
+ formState: store.formState,
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders all buttons as enabled', () => {
+ expect(
+ vm.$el.querySelectorAll('.disabled').length,
+ ).toBe(0);
+
+ expect(
+ vm.$el.querySelectorAll('[disabled]').length,
+ ).toBe(0);
+ });
+
+ it('does not render delete button if canUpdate is false', (done) => {
+ vm.canDestroy = false;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-danger'),
+ ).toBeNull();
+
+ done();
+ });
+ });
+
+ it('disables submit button when title is blank', (done) => {
+ vm.formState.title = '';
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-save').getAttribute('disabled'),
+ ).toBe('disabled');
+
+ done();
+ });
+ });
+
+ describe('updateIssuable', () => {
+ it('sends update.issauble event when clicking save button', () => {
+ vm.$el.querySelector('.btn-save').click();
+
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('update.issuable');
+ });
+
+ it('shows loading icon after clicking save button', (done) => {
+ vm.$el.querySelector('.btn-save').click();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-save .fa'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('disabled button after clicking save button', (done) => {
+ vm.$el.querySelector('.btn-save').click();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-save').getAttribute('disabled'),
+ ).toBe('disabled');
+
+ done();
+ });
+ });
+ });
+
+ describe('closeForm', () => {
+ it('emits close.form when clicking cancel', () => {
+ vm.$el.querySelector('.btn-default').click();
+
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+ });
+ });
+
+ describe('deleteIssuable', () => {
+ it('sends delete.issuable event when clicking save button', () => {
+ spyOn(window, 'confirm').and.returnValue(true);
+ vm.$el.querySelector('.btn-danger').click();
+
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('delete.issuable');
+ });
+
+ it('shows loading icon after clicking delete button', (done) => {
+ spyOn(window, 'confirm').and.returnValue(true);
+ vm.$el.querySelector('.btn-danger').click();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-danger .fa'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('does no actions when confirm is false', (done) => {
+ spyOn(window, 'confirm').and.returnValue(false);
+ vm.$el.querySelector('.btn-danger').click();
+
+ Vue.nextTick(() => {
+ expect(
+ eventHub.$emit,
+ ).not.toHaveBeenCalledWith('delete.issuable');
+ expect(
+ vm.$el.querySelector('.btn-danger .fa'),
+ ).toBeNull();
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/components/fields/description_spec.js b/spec/javascripts/issue_show/components/fields/description_spec.js
new file mode 100644
index 00000000000..f5b35b1e8b0
--- /dev/null
+++ b/spec/javascripts/issue_show/components/fields/description_spec.js
@@ -0,0 +1,56 @@
+import Vue from 'vue';
+import Store from '~/issue_show/stores';
+import descriptionField from '~/issue_show/components/fields/description.vue';
+
+describe('Description field component', () => {
+ let vm;
+ let store;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(descriptionField);
+ const el = document.createElement('div');
+ store = new Store({
+ titleHtml: '',
+ descriptionHtml: '',
+ issuableRef: '',
+ });
+ store.formState.description = 'test';
+
+ document.body.appendChild(el);
+
+ vm = new Component({
+ el,
+ propsData: {
+ markdownPreviewUrl: '/',
+ markdownDocs: '/',
+ formState: store.formState,
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders markdown field with description', () => {
+ expect(
+ vm.$el.querySelector('.md-area textarea').value,
+ ).toBe('test');
+ });
+
+ it('renders markdown field with a markdown description', (done) => {
+ store.formState.description = '**test**';
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.md-area textarea').value,
+ ).toBe('**test**');
+
+ done();
+ });
+ });
+
+ it('focuses field when mounted', () => {
+ expect(
+ document.activeElement,
+ ).toBe(vm.$refs.textarea);
+ });
+});
diff --git a/spec/javascripts/issue_show/components/fields/description_template_spec.js b/spec/javascripts/issue_show/components/fields/description_template_spec.js
new file mode 100644
index 00000000000..2b7ee65094b
--- /dev/null
+++ b/spec/javascripts/issue_show/components/fields/description_template_spec.js
@@ -0,0 +1,49 @@
+import Vue from 'vue';
+import descriptionTemplate from '~/issue_show/components/fields/description_template.vue';
+import '~/templates/issuable_template_selector';
+import '~/templates/issuable_template_selectors';
+
+describe('Issue description template component', () => {
+ let vm;
+ let formState;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(descriptionTemplate);
+ formState = {
+ description: 'test',
+ };
+
+ vm = new Component({
+ propsData: {
+ formState,
+ issuableTemplates: [{ name: 'test' }],
+ projectPath: '/',
+ projectNamespace: '/',
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders templates as JSON array in data attribute', () => {
+ expect(
+ vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data'),
+ ).toBe('[{"name":"test"}]');
+ });
+
+ it('updates formState when changing template', () => {
+ vm.issuableTemplate.editor.setValue('test new template');
+
+ expect(
+ formState.description,
+ ).toBe('test new template');
+ });
+
+ it('returns formState description with editor getValue', () => {
+ formState.description = 'testing new template';
+
+ expect(
+ vm.issuableTemplate.editor.getValue(),
+ ).toBe('testing new template');
+ });
+});
diff --git a/spec/javascripts/issue_show/components/fields/project_move_spec.js b/spec/javascripts/issue_show/components/fields/project_move_spec.js
new file mode 100644
index 00000000000..86d35c33ff4
--- /dev/null
+++ b/spec/javascripts/issue_show/components/fields/project_move_spec.js
@@ -0,0 +1,38 @@
+import Vue from 'vue';
+import projectMove from '~/issue_show/components/fields/project_move.vue';
+
+describe('Project move field component', () => {
+ let vm;
+ let formState;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(projectMove);
+
+ formState = {
+ move_to_project_id: 0,
+ };
+
+ vm = new Component({
+ propsData: {
+ formState,
+ projectsAutocompleteUrl: '/autocomplete',
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('mounts select2 element', () => {
+ expect(
+ vm.$el.querySelector('.select2-container'),
+ ).not.toBeNull();
+ });
+
+ it('updates formState on change', () => {
+ $(vm.$refs['move-dropdown']).val(2).trigger('change');
+
+ expect(
+ formState.move_to_project_id,
+ ).toBe(2);
+ });
+});
diff --git a/spec/javascripts/issue_show/components/fields/title_spec.js b/spec/javascripts/issue_show/components/fields/title_spec.js
new file mode 100644
index 00000000000..53ae038a6a2
--- /dev/null
+++ b/spec/javascripts/issue_show/components/fields/title_spec.js
@@ -0,0 +1,30 @@
+import Vue from 'vue';
+import Store from '~/issue_show/stores';
+import titleField from '~/issue_show/components/fields/title.vue';
+
+describe('Title field component', () => {
+ let vm;
+ let store;
+
+ beforeEach(() => {
+ const Component = Vue.extend(titleField);
+ store = new Store({
+ titleHtml: '',
+ descriptionHtml: '',
+ issuableRef: '',
+ });
+ store.formState.title = 'test';
+
+ vm = new Component({
+ propsData: {
+ formState: store.formState,
+ },
+ }).$mount();
+ });
+
+ it('renders form control with formState title', () => {
+ expect(
+ vm.$el.querySelector('.form-control').value,
+ ).toBe('test');
+ });
+});
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
new file mode 100644
index 00000000000..9a85223208c
--- /dev/null
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -0,0 +1,68 @@
+import Vue from 'vue';
+import formComponent from '~/issue_show/components/form.vue';
+import '~/templates/issuable_template_selector';
+import '~/templates/issuable_template_selectors';
+
+describe('Inline edit form component', () => {
+ let vm;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(formComponent);
+
+ vm = new Component({
+ propsData: {
+ canDestroy: true,
+ canMove: true,
+ formState: {
+ title: 'b',
+ description: 'a',
+ lockedWarningVisible: false,
+ },
+ markdownPreviewUrl: '/',
+ markdownDocs: '/',
+ projectsAutocompleteUrl: '/',
+ projectPath: '/',
+ projectNamespace: '/',
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('does not render template selector if no templates exist', () => {
+ expect(
+ vm.$el.querySelector('.js-issuable-selector-wrap'),
+ ).toBeNull();
+ });
+
+ it('renders template selector when templates exists', (done) => {
+ spyOn(gl, 'IssuableTemplateSelectors');
+ vm.issuableTemplates = ['test'];
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.js-issuable-selector-wrap'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('hides locked warning by default', () => {
+ expect(
+ vm.$el.querySelector('.alert'),
+ ).toBeNull();
+ });
+
+ it('shows locked warning if formState is different', (done) => {
+ vm.formState.lockedWarningVisible = true;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.alert'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/components/title_spec.js b/spec/javascripts/issue_show/components/title_spec.js
index 2f953e7e92e..a2d90a9b9f5 100644
--- a/spec/javascripts/issue_show/components/title_spec.js
+++ b/spec/javascripts/issue_show/components/title_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import Store from '~/issue_show/stores';
import titleComponent from '~/issue_show/components/title.vue';
describe('Title component', () => {
@@ -6,11 +7,18 @@ describe('Title component', () => {
beforeEach(() => {
const Component = Vue.extend(titleComponent);
+ const store = new Store({
+ titleHtml: '',
+ descriptionHtml: '',
+ issuableRef: '',
+ });
vm = new Component({
propsData: {
issuableRef: '#1',
titleHtml: 'Testing <img />',
titleText: 'Testing',
+ showForm: false,
+ formState: store.formState,
},
}).$mount();
});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index e9bffd74d90..e3938a77680 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -356,7 +356,7 @@ import '~/lib/utils/common_utils';
describe('gl.utils.setCiStatusFavicon', () => {
it('should set page favicon to CI status favicon based on provided status', () => {
- const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`;
+ const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
const FAVICON_PATH = '//icon_status_success';
const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js
index 90b12c9f115..83c92deccdc 100644
--- a/spec/javascripts/lib/utils/number_utility_spec.js
+++ b/spec/javascripts/lib/utils/number_utility_spec.js
@@ -1,4 +1,4 @@
-import { formatRelevantDigits, bytesToKiB } from '~/lib/utils/number_utils';
+import { formatRelevantDigits, bytesToKiB, bytesToMiB } from '~/lib/utils/number_utils';
describe('Number Utils', () => {
describe('formatRelevantDigits', () => {
@@ -45,4 +45,11 @@ describe('Number Utils', () => {
expect(bytesToKiB(1000)).toEqual(0.9765625);
});
});
+
+ describe('bytesToMiB', () => {
+ it('calculates MiB for the given bytes', () => {
+ expect(bytesToMiB(1048576)).toEqual(1);
+ expect(bytesToMiB(1000000)).toEqual(0.95367431640625);
+ });
+ });
});
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 1173fa40947..f444bcaf847 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -13,7 +13,9 @@ import '~/merge_request';
});
it('modifies the Markdown field', function() {
spyOn(jQuery, 'ajax').and.stub();
- $('input[type=checkbox]').attr('checked', true).trigger('change');
+ const changeEvent = document.createEvent('HTMLEvents');
+ changeEvent.initEvent('change', true, true);
+ $('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent);
return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
return it('submits an ajax request on tasklist:changed', function() {
diff --git a/spec/javascripts/notebook/cells/markdown_spec.js b/spec/javascripts/notebook/cells/markdown_spec.js
index 38c976f38d8..a88e9ed3d99 100644
--- a/spec/javascripts/notebook/cells/markdown_spec.js
+++ b/spec/javascripts/notebook/cells/markdown_spec.js
@@ -1,8 +1,11 @@
import Vue from 'vue';
import MarkdownComponent from '~/notebook/cells/markdown.vue';
+import katex from 'vendor/katex';
const Component = Vue.extend(MarkdownComponent);
+window.katex = katex;
+
describe('Markdown component', () => {
let vm;
let cell;
@@ -38,4 +41,58 @@ describe('Markdown component', () => {
it('renders the markdown HTML', () => {
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
});
+
+ describe('katex', () => {
+ beforeEach(() => {
+ json = getJSONFixture('blob/notebook/math.json');
+ });
+
+ it('renders multi-line katex', (done) => {
+ vm = new Component({
+ propsData: {
+ cell: json.cells[0],
+ },
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.katex'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('renders inline katex', (done) => {
+ vm = new Component({
+ propsData: {
+ cell: json.cells[1],
+ },
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('p:first-child .katex'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('renders multiple inline katex', (done) => {
+ vm = new Component({
+ propsData: {
+ cell: json.cells[1],
+ },
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelectorAll('p:nth-child(2) .katex').length,
+ ).toBe(4);
+
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 025f08ee332..17aa70ff3f1 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -34,7 +34,9 @@ import '~/notes';
});
it('modifies the Markdown field', function() {
- $('input[type=checkbox]').attr('checked', true).trigger('change');
+ const changeEvent = document.createEvent('HTMLEvents');
+ changeEvent.initEvent('change', true, true);
+ $('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent);
expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
@@ -128,7 +130,6 @@ import '~/notes';
beforeEach(() => {
note = {
id: 1,
- discussion_html: null,
valid: true,
note: 'heya',
html: '<div>heya</div>',
diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js
index f033956c071..85bd87318db 100644
--- a/spec/javascripts/pipelines/graph/action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/action_component_spec.js
@@ -4,7 +4,7 @@ import actionComponent from '~/pipelines/components/graph/action_component.vue';
describe('pipeline graph action component', () => {
let component;
- beforeEach(() => {
+ beforeEach((done) => {
const ActionComponent = Vue.extend(actionComponent);
component = new ActionComponent({
propsData: {
@@ -14,6 +14,8 @@ describe('pipeline graph action component', () => {
actionIcon: 'icon_action_cancel',
},
}).$mount();
+
+ Vue.nextTick(done);
});
it('should render a link', () => {
@@ -27,7 +29,7 @@ describe('pipeline graph action component', () => {
it('should update bootstrap tooltip when title changes', (done) => {
component.tooltipText = 'changed';
- Vue.nextTick(() => {
+ setTimeout(() => {
expect(component.$el.getAttribute('data-original-title')).toBe('changed');
done();
});
diff --git a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js b/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js
index 14ff1b0d25c..25fd18b197e 100644
--- a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js
@@ -4,7 +4,7 @@ import dropdownActionComponent from '~/pipelines/components/graph/dropdown_actio
describe('action component', () => {
let component;
- beforeEach(() => {
+ beforeEach((done) => {
const DropdownActionComponent = Vue.extend(dropdownActionComponent);
component = new DropdownActionComponent({
propsData: {
@@ -14,6 +14,8 @@ describe('action component', () => {
actionIcon: 'icon_action_cancel',
},
}).$mount();
+
+ Vue.nextTick(done);
});
it('should render a link', () => {
diff --git a/spec/javascripts/pipelines/graph/graph_component_spec.js b/spec/javascripts/pipelines/graph/graph_component_spec.js
index 6bd0eb86263..713baa65a17 100644
--- a/spec/javascripts/pipelines/graph/graph_component_spec.js
+++ b/spec/javascripts/pipelines/graph/graph_component_spec.js
@@ -14,49 +14,42 @@ describe('graph component', () => {
describe('while is loading', () => {
it('should render a loading icon', () => {
- const component = new GraphComponent().$mount('#js-pipeline-graph-vue');
+ const component = new GraphComponent({
+ propsData: {
+ isLoading: true,
+ pipeline: {},
+ },
+ }).$mount('#js-pipeline-graph-vue');
expect(component.$el.querySelector('.loading-icon')).toBeDefined();
});
});
- describe('with a successfull response', () => {
- const interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify(graphJSON), {
- status: 200,
- }));
- };
+ describe('with data', () => {
+ it('should render the graph', () => {
+ const component = new GraphComponent({
+ propsData: {
+ isLoading: false,
+ pipeline: graphJSON,
+ },
+ }).$mount('#js-pipeline-graph-vue');
- beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
- });
-
- it('should render the graph', (done) => {
- const component = new GraphComponent().$mount('#js-pipeline-graph-vue');
-
- setTimeout(() => {
- expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
+ expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
- expect(
- component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'),
- ).toEqual(true);
+ expect(
+ component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'),
+ ).toEqual(true);
- expect(
- component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'),
- ).toEqual(true);
+ expect(
+ component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'),
+ ).toEqual(true);
- expect(
- component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'),
- ).toEqual(true);
+ expect(
+ component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'),
+ ).toEqual(true);
- expect(component.$el.querySelector('loading-icon')).toBe(null);
+ expect(component.$el.querySelector('loading-icon')).toBe(null);
- expect(component.$el.querySelector('.stage-column-list')).toBeDefined();
- done();
- }, 0);
+ expect(component.$el.querySelector('.stage-column-list')).toBeDefined();
});
});
});
diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js
index 63986b6c0db..e90593e0f40 100644
--- a/spec/javascripts/pipelines/graph/job_component_spec.js
+++ b/spec/javascripts/pipelines/graph/job_component_spec.js
@@ -27,26 +27,30 @@ describe('pipeline graph job component', () => {
});
describe('name with link', () => {
- it('should render the job name and status with a link', () => {
+ it('should render the job name and status with a link', (done) => {
const component = new JobComponent({
propsData: {
job: mockJob,
},
}).$mount();
- const link = component.$el.querySelector('a');
+ Vue.nextTick(() => {
+ const link = component.$el.querySelector('a');
- expect(link.getAttribute('href')).toEqual(mockJob.status.details_path);
+ expect(link.getAttribute('href')).toEqual(mockJob.status.details_path);
- expect(
- link.getAttribute('data-original-title'),
- ).toEqual(`${mockJob.name} - ${mockJob.status.label}`);
+ expect(
+ link.getAttribute('data-original-title'),
+ ).toEqual(`${mockJob.name} - ${mockJob.status.label}`);
- expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined();
+ expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined();
- expect(
- component.$el.querySelector('.ci-status-text').textContent.trim(),
- ).toEqual(mockJob.name);
+ expect(
+ component.$el.querySelector('.ci-status-text').textContent.trim(),
+ ).toEqual(mockJob.name);
+
+ done();
+ });
});
});
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index 0bcc3905702..d74b1281668 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import pipelineUrlComp from '~/pipelines/components/pipeline_url';
+import pipelineUrlComp from '~/pipelines/components/pipeline_url.vue';
describe('Pipeline Url Component', () => {
let PipelineUrlComponent;
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index da9dff18ada..2c3d0ddff28 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -7,6 +7,18 @@ const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
const metricsMockData = {
success: true,
metrics: {
+ memory_before: [
+ {
+ metric: {},
+ value: [1495785220.607, '9572875.906976745'],
+ },
+ ],
+ memory_after: [
+ {
+ metric: {},
+ value: [1495787020.607, '4485853.130206379'],
+ },
+ ],
memory_values: [
{
metric: {},
@@ -39,7 +51,7 @@ const createComponent = () => {
const messages = {
loadingMetrics: 'Loading deployment statistics.',
- hasMetrics: 'Deployment memory usage:',
+ hasMetrics: 'Memory usage unchanged from 0MB to 0MB',
loadFailed: 'Failed to load deployment statistics.',
metricsUnavailable: 'Deployment statistics are not available currently.',
};
@@ -89,17 +101,52 @@ describe('MemoryUsage', () => {
});
});
+ describe('computed', () => {
+ describe('memoryChangeType', () => {
+ it('should return "increased" if memoryFrom value is less than memoryTo value', () => {
+ vm.memoryFrom = 4.28;
+ vm.memoryTo = 9.13;
+
+ expect(vm.memoryChangeType).toEqual('increased');
+ });
+
+ it('should return "decreased" if memoryFrom value is less than memoryTo value', () => {
+ vm.memoryFrom = 9.13;
+ vm.memoryTo = 4.28;
+
+ expect(vm.memoryChangeType).toEqual('decreased');
+ });
+
+ it('should return "unchanged" if memoryFrom value equal to memoryTo value', () => {
+ vm.memoryFrom = 1;
+ vm.memoryTo = 1;
+
+ expect(vm.memoryChangeType).toEqual('unchanged');
+ });
+ });
+ });
+
describe('methods', () => {
const { metrics, deployment_time } = metricsMockData;
+ describe('getMegabytes', () => {
+ it('should return Megabytes from provided Bytes value', () => {
+ const memoryInBytes = '9572875.906976745';
+
+ expect(vm.getMegabytes(memoryInBytes)).toEqual('9.13');
+ });
+ });
+
describe('computeGraphData', () => {
it('should populate sparkline graph', () => {
vm.computeGraphData(metrics, deployment_time);
- const { hasMetrics, memoryMetrics, deploymentTime } = vm;
+ const { hasMetrics, memoryMetrics, deploymentTime, memoryFrom, memoryTo } = vm;
expect(hasMetrics).toBeTruthy();
expect(memoryMetrics.length > 0).toBeTruthy();
expect(deploymentTime).toEqual(deployment_time);
+ expect(memoryFrom).toEqual('9.13');
+ expect(memoryTo).toEqual('4.28');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index d043ad38b8b..732b516badd 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -5,7 +5,7 @@ import * as simplePoll from '~/lib/utils/simple_poll';
const commitMessage = 'This is the commit message';
const commitMessageWithDescription = 'This is the commit message description';
-const createComponent = () => {
+const createComponent = (customConfig = {}) => {
const Component = Vue.extend(readyToMergeComponent);
const mr = {
isPipelineActive: false,
@@ -17,8 +17,12 @@ const createComponent = () => {
sha: '12345678',
commitMessage,
commitMessageWithDescription,
+ shouldRemoveSourceBranch: true,
+ canRemoveSourceBranch: false,
};
+ Object.assign(mr, customConfig.mr);
+
const service = {
merge() {},
poll() {},
@@ -51,7 +55,6 @@ describe('MRWidgetReadyToMerge', () => {
describe('data', () => {
it('should have default data', () => {
- expect(vm.removeSourceBranch).toBeTruthy(true);
expect(vm.mergeWhenBuildSucceeds).toBeFalsy();
expect(vm.useCommitMessageWithDescription).toBeFalsy();
expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy();
@@ -166,6 +169,36 @@ describe('MRWidgetReadyToMerge', () => {
expect(vm.isMergeButtonDisabled).toBeTruthy();
});
});
+
+ describe('Remove source branch checkbox', () => {
+ describe('when user can merge but cannot delete branch', () => {
+ it('isRemoveSourceBranchButtonDisabled should be true', () => {
+ expect(vm.isRemoveSourceBranchButtonDisabled).toBe(true);
+ });
+
+ it('should be disabled in the rendered output', () => {
+ const checkboxElement = vm.$el.querySelector('#remove-source-branch-input');
+ expect(checkboxElement.getAttribute('disabled')).toBe('disabled');
+ });
+ });
+
+ describe('when user can merge and can delete branch', () => {
+ beforeEach(() => {
+ this.customVm = createComponent({
+ mr: { canRemoveSourceBranch: true },
+ });
+ });
+
+ it('isRemoveSourceBranchButtonDisabled should be false', () => {
+ expect(this.customVm.isRemoveSourceBranchButtonDisabled).toBe(false);
+ });
+
+ it('should be enabled in rendered output', () => {
+ const checkboxElement = this.customVm.$el.querySelector('#remove-source-branch-input');
+ expect(checkboxElement.getAttribute('disabled')).toBeNull();
+ });
+ });
+ });
});
describe('methods', () => {
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index bdc18243a15..3a0c50b750f 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options';
import eventHub from '~/vue_merge_request_widget/event_hub';
+import notify from '~/lib/utils/notify';
import mockData from './mock_data';
const createComponent = () => {
@@ -107,6 +108,8 @@ describe('mrWidgetOptions', () => {
it('should tell service to check status', (done) => {
spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData));
spyOn(vm.mr, 'setData');
+ spyOn(vm, 'handleNotification');
+
let isCbExecuted = false;
const cb = () => {
isCbExecuted = true;
@@ -117,6 +120,7 @@ describe('mrWidgetOptions', () => {
setTimeout(() => {
expect(vm.service.checkStatus).toHaveBeenCalled();
expect(vm.mr.setData).toHaveBeenCalled();
+ expect(vm.handleNotification).toHaveBeenCalledWith(mockData);
expect(isCbExecuted).toBeTruthy();
done();
}, 333);
@@ -254,6 +258,39 @@ describe('mrWidgetOptions', () => {
});
});
+ describe('handleNotification', () => {
+ const data = {
+ ci_status: 'running',
+ title: 'title',
+ pipeline: { details: { status: { label: 'running-label' } } },
+ };
+
+ beforeEach(() => {
+ spyOn(notify, 'notifyMe');
+
+ vm.mr.ciStatus = 'failed';
+ vm.mr.gitlabLogo = 'logo.png';
+ });
+
+ it('should call notifyMe', () => {
+ vm.handleNotification(data);
+
+ expect(notify.notifyMe).toHaveBeenCalledWith(
+ 'Pipeline running-label',
+ 'Pipeline running-label for "title"',
+ 'logo.png',
+ );
+ });
+
+ it('should not call notifyMe if the status has not changed', () => {
+ vm.mr.ciStatus = data.ci_status;
+
+ vm.handleNotification(data);
+
+ expect(notify.notifyMe).not.toHaveBeenCalled();
+ });
+ });
+
describe('resumePolling', () => {
it('should call stopTimer on pollingInterval', () => {
spyOn(vm.pollingInterval, 'resume');
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
new file mode 100644
index 00000000000..4bbaff561fc
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -0,0 +1,121 @@
+import Vue from 'vue';
+import fieldComponent from '~/vue_shared/components/markdown/field.vue';
+
+describe('Markdown field component', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = new Vue({
+ render(createElement) {
+ return createElement(
+ fieldComponent,
+ {
+ props: {
+ markdownPreviewUrl: '/preview',
+ markdownDocs: '/docs',
+ },
+ },
+ [
+ createElement('textarea', {
+ slot: 'textarea',
+ }),
+ ],
+ );
+ },
+ });
+ });
+
+ it('creates a new instance of GL form', (done) => {
+ spyOn(gl, 'GLForm');
+ vm.$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ gl.GLForm,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ describe('mounted', () => {
+ beforeEach((done) => {
+ vm.$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders textarea inside backdrop', () => {
+ expect(
+ vm.$el.querySelector('.zen-backdrop textarea'),
+ ).not.toBeNull();
+ });
+
+ describe('markdown preview', () => {
+ let previewLink;
+
+ beforeEach(() => {
+ spyOn(Vue.http, 'post').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ body: '<p>markdown preview</p>',
+ };
+ },
+ });
+ }));
+
+ previewLink = vm.$el.querySelector('.nav-links li:nth-child(2) a');
+ });
+
+ it('sets preview link as active', (done) => {
+ previewLink.click();
+
+ Vue.nextTick(() => {
+ expect(
+ previewLink.parentNode.classList.contains('active'),
+ ).toBeTruthy();
+
+ done();
+ });
+ });
+
+ it('shows preview loading text', (done) => {
+ previewLink.click();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.md-preview').textContent.trim(),
+ ).toContain('Loading...');
+
+ done();
+ });
+ });
+
+ it('renders markdown preview', (done) => {
+ previewLink.click();
+
+ setTimeout(() => {
+ expect(
+ vm.$el.querySelector('.md-preview').innerHTML,
+ ).toContain('<p>markdown preview</p>');
+
+ done();
+ });
+ });
+
+ it('renders GFM with jQuery', (done) => {
+ spyOn($.fn, 'renderGFM');
+ previewLink.click();
+
+ setTimeout(() => {
+ expect(
+ $.fn.renderGFM,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js
new file mode 100644
index 00000000000..7110ff36937
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js
@@ -0,0 +1,67 @@
+import Vue from 'vue';
+import headerComponent from '~/vue_shared/components/markdown/header.vue';
+
+describe('Markdown field header component', () => {
+ let vm;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(headerComponent);
+
+ vm = new Component({
+ propsData: {
+ previewMarkdown: false,
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders markdown buttons', () => {
+ expect(
+ vm.$el.querySelectorAll('.js-md').length,
+ ).toBe(7);
+ });
+
+ it('renders `write` link as active when previewMarkdown is false', () => {
+ expect(
+ vm.$el.querySelector('li:nth-child(1)').classList.contains('active'),
+ ).toBeTruthy();
+ });
+
+ it('renders `preview` link as active when previewMarkdown is true', (done) => {
+ vm.previewMarkdown = true;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('li:nth-child(2)').classList.contains('active'),
+ ).toBeTruthy();
+
+ done();
+ });
+ });
+
+ it('emits toggle markdown event when clicking preview', () => {
+ spyOn(vm, '$emit');
+
+ vm.$el.querySelector('li:nth-child(2) a').click();
+
+ expect(
+ vm.$emit,
+ ).toHaveBeenCalledWith('toggle-markdown');
+ });
+
+ it('blurs preview link after click', (done) => {
+ const link = vm.$el.querySelector('li:nth-child(2) a');
+ spyOn(HTMLElement.prototype, 'blur');
+
+ link.click();
+
+ setTimeout(() => {
+ expect(
+ link.blur,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+});
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 63b23dac7ed..edf3846b742 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -16,6 +16,11 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to eq(exp)
end
+ it 'ignores references with text before the @ sign' do
+ exp = act = "Hey foo#{reference}"
+ expect(reference_filter(act).to_html).to eq(exp)
+ end
+
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 4ec998efe53..592ed0d2b98 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -42,6 +42,29 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
expect(subject.referenced_by([link])).to eq([user])
end
+
+ context 'when RequestStore is active' do
+ let(:other_user) { create(:user) }
+
+ before do
+ RequestStore.begin!
+ end
+
+ after do
+ RequestStore.end!
+ RequestStore.clear!
+ end
+
+ it 'does not return users from the first call in the second' do
+ link['data-user'] = user.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([user])
+
+ link['data-user'] = other_user.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([other_user])
+ end
+ end
end
context 'when the link has a data-project attribute' do
@@ -74,7 +97,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
end
end
- describe '#nodes_visible_to_use?' do
+ describe '#nodes_visible_to_user' do
context 'when the link has a data-group attribute' do
context 'using an existing group ID' do
before do
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
new file mode 100644
index 00000000000..1d92a5cb33f
--- /dev/null
+++ b/spec/lib/feature_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Feature, lib: true do
+ describe '.get' do
+ let(:feature) { double(:feature) }
+ let(:key) { 'my_feature' }
+
+ it 'returns the Flipper feature' do
+ expect_any_instance_of(Flipper::DSL).to receive(:feature).with(key).
+ and_return(feature)
+
+ expect(described_class.get(key)).to be(feature)
+ end
+ end
+
+ describe '.all' do
+ let(:features) { Set.new }
+
+ it 'returns the Flipper features as an array' do
+ expect_any_instance_of(Flipper::DSL).to receive(:features).
+ and_return(features)
+
+ expect(described_class.all).to eq(features.to_a)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index b386852b196..cfb5cba054e 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
- let(:project) { create(:project) }
+ let!(:project) { create(:project) }
let(:pipeline_status) { described_class.new(project) }
let(:cache_key) { "projects/#{project.id}/pipeline_status" }
@@ -18,7 +18,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
let(:sha) { '424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6' }
let(:ref) { 'master' }
let(:pipeline_info) { { sha: sha, status: status, ref: ref } }
- let(:project_without_status) { create(:project) }
+ let!(:project_without_status) { create(:project) }
describe '.load_in_batch_for_projects' do
it 'preloads pipeline_status on projects' do
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index eb4f06b371c..13e6953147b 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -58,9 +58,12 @@ describe Gitlab::ChatCommands::Command, service: true do
end
end
- context 'and user does have deployment permission' do
+ context 'and user has deployment permission' do
before do
- build.project.add_master(user)
+ build.project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'returns action' do
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
index b33389d959e..46dbdeae37c 100644
--- a/spec/lib/gitlab/chat_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -7,7 +7,12 @@ describe Gitlab::ChatCommands::Deploy, service: true do
let(:regex_match) { described_class.match('deploy staging to production') }
before do
- project.add_master(user)
+ # Make it possible to trigger protected manual actions for developers.
+ #
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
subject do
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index 40b96b1807b..72bd7c4eb93 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -31,7 +31,7 @@ describe Gitlab::Ci::Status::Build::Common do
describe '#details_path' do
it 'links to the build details page' do
- expect(subject.details_path).to include "builds/#{build.id}"
+ expect(subject.details_path).to include "jobs/#{build.id}"
end
end
end
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 185bb9098da..3f30b2c38f2 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -224,7 +224,10 @@ describe Gitlab::Ci::Status::Build::Factory do
context 'when user has ability to play action' do
before do
- build.project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'fabricates status that has action' do
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index f5d0f977768..0e15a5f3c6b 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::Ci::Status::Build::Play do
let(:user) { create(:user) }
+ let(:project) { build.project }
let(:build) { create(:ci_build, :manual) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
@@ -15,8 +16,13 @@ describe Gitlab::Ci::Status::Build::Play do
describe '#has_action?' do
context 'when user is allowed to update build' do
- context 'when user can push to branch' do
- before { build.project.add_master(user) }
+ context 'when user is allowed to trigger protected action' do
+ before do
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
+ end
it { is_expected.to have_action }
end
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index ae617b313c5..3565e719ad3 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -6,8 +6,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
iterator,
max_files: max_files,
max_lines: max_lines,
- all_diffs: all_diffs,
- no_collapse: no_collapse
+ limits: limits,
+ expanded: expanded
)
end
let(:iterator) { MutatingConstantIterator.new(file_count, fake_diff(line_length, line_count)) }
@@ -16,8 +16,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
let(:line_count) { 1 }
let(:max_files) { 10 }
let(:max_lines) { 100 }
- let(:all_diffs) { false }
- let(:no_collapse) { true }
+ let(:limits) { true }
+ let(:expanded) { true }
describe '#to_a' do
subject { super().to_a }
@@ -75,7 +75,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -94,7 +94,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
describe '#size' do
it { expect(subject.size).to eq(3) }
-
+
it 'does not change after peeking' do
subject.any?
expect(subject.size).to eq(3)
@@ -123,7 +123,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
it { expect(subject.size).to eq(0) }
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -167,7 +167,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
it { expect(subject.size).to eq(10) }
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -207,7 +207,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
it { expect(subject.size).to eq(3) }
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -273,7 +273,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
it { expect(subject.size).to eq(9) }
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -344,7 +344,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
let(:iterator) { [{ diff: 'a' * 20480 }] }
context 'when no collapse is set' do
- let(:no_collapse) { true }
+ let(:expanded) { true }
it 'yields Diff instances even when they are quite big' do
expect { |b| subject.each(&b) }.
@@ -363,7 +363,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
context 'when no collapse is unset' do
- let(:no_collapse) { false }
+ let(:expanded) { false }
it 'yields Diff instances even when they are quite big' do
expect { |b| subject.each(&b) }.
@@ -450,7 +450,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
it 'yields Diff instances even when they are quite big' do
expect { |b| subject.each(&b) }.
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 4189aaef643..8e24168ad71 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -85,12 +85,12 @@ EOT
# The patch total size is 200, with lines between 21 and 54.
# This is a quick-and-dirty way to test this. Ideally, a new patch is
# added to the test repo with a size that falls between the real limits.
- stub_const("#{described_class}::DIFF_SIZE_LIMIT", 150)
- stub_const("#{described_class}::DIFF_COLLAPSE_LIMIT", 100)
+ stub_const("#{described_class}::SIZE_LIMIT", 150)
+ stub_const("#{described_class}::COLLAPSE_LIMIT", 100)
end
it 'prunes the diff as a large diff instead of as a collapsed diff' do
- diff = described_class.new(@rugged_diff, collapse: true)
+ diff = described_class.new(@rugged_diff, expanded: false)
expect(diff.diff).to be_empty
expect(diff).to be_too_large
@@ -269,7 +269,7 @@ EOT
it 'returns true for a diff that was explicitly marked as being too large' do
diff = described_class.new(diff: 'a')
- diff.prune_large_diff!
+ diff.too_large!
expect(diff.too_large?).to eq(true)
end
@@ -291,31 +291,31 @@ EOT
it 'returns true for a diff that was explicitly marked as being collapsed' do
diff = described_class.new(diff: 'a')
- diff.prune_collapsed_diff!
+ diff.collapse!
expect(diff).to be_collapsed
end
end
- describe '#collapsible?' do
+ describe '#collapsed?' do
it 'returns true for a diff that is quite large' do
- diff = described_class.new(diff: 'a' * 20480)
+ diff = described_class.new({ diff: 'a' * 20480 }, expanded: false)
- expect(diff).to be_collapsible
+ expect(diff).to be_collapsed
end
it 'returns false for a diff that is small enough' do
- diff = described_class.new(diff: 'a')
+ diff = described_class.new({ diff: 'a' }, expanded: false)
- expect(diff).not_to be_collapsible
+ expect(diff).not_to be_collapsed
end
end
- describe '#prune_collapsed_diff!' do
+ describe '#collapse!' do
it 'prunes the diff' do
diff = described_class.new(diff: "foo\nbar")
- diff.prune_collapsed_diff!
+ diff.collapse!
expect(diff.diff).to eq('')
expect(diff.line_count).to eq(0)
diff --git a/spec/lib/gitlab/git/encoding_helper_spec.rb b/spec/lib/gitlab/git/encoding_helper_spec.rb
index 1a3bf802a07..48fc817d857 100644
--- a/spec/lib/gitlab/git/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/git/encoding_helper_spec.rb
@@ -2,7 +2,7 @@ require "spec_helper"
describe Gitlab::Git::EncodingHelper do
let(:ext_class) { Class.new { extend Gitlab::Git::EncodingHelper } }
- let(:binary_string) { File.join(SEED_STORAGE_PATH, 'gitlab_logo.png') }
+ let(:binary_string) { File.read(Rails.root + "spec/fixtures/dk.png") }
describe '#encode!' do
[
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index cb107c6d1f9..9d0e95d5b19 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -381,6 +381,19 @@ describe Gitlab::Git::Repository, seed_helper: true do
}
])
end
+
+ it 'should not break on invalid syntax' do
+ allow(repository).to receive(:blob_content).and_return(<<-GITMODULES.strip_heredoc)
+ [submodule "six"]
+ path = six
+ url = git://github.com/randx/six.git
+
+ [submodule]
+ foo = bar
+ GITMODULES
+
+ expect(submodules).to have_key('six')
+ end
end
context 'where repo doesn\'t have submodules' do
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 08ee0dff6b2..95ecba67532 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -1,7 +1,10 @@
require 'spec_helper'
-describe Gitlab::GitalyClient, lib: true do
+# We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
+# those stubs while testing the GitalyClient itself.
+describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do
describe '.stub' do
+ # Notice that this is referring to gRPC "stubs", not rspec stubs
before { described_class.clear_stubs! }
context 'when passed a UNIX socket address' do
@@ -32,4 +35,81 @@ describe Gitlab::GitalyClient, lib: true do
end
end
end
+
+ describe 'feature_enabled?' do
+ let(:feature_name) { 'my_feature' }
+ let(:real_feature_name) { "gitaly_#{feature_name}" }
+
+ context 'when Gitaly is disabled' do
+ before { allow(described_class).to receive(:enabled?).and_return(false) }
+
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name)).to be(false)
+ end
+ end
+
+ context 'when the feature status is DISABLED' do
+ let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::DISABLED }
+
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+
+ context 'when the feature_status is OPT_IN' do
+ let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::OPT_IN }
+
+ context "when the feature flag hasn't been set" do
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+
+ context "when the feature flag is set to disable" do
+ before { Feature.get(real_feature_name).disable }
+
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+
+ context "when the feature flag is set to enable" do
+ before { Feature.get(real_feature_name).enable }
+
+ it 'returns true' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
+ end
+ end
+
+ context "when the feature flag is set to a percentage of time" do
+ before { Feature.get(real_feature_name).enable_percentage_of_time(70) }
+
+ it 'bases the result on pseudo-random numbers' do
+ expect(Random).to receive(:rand).and_return(0.3)
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
+
+ expect(Random).to receive(:rand).and_return(0.8)
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+ end
+
+ context 'when the feature_status is OPT_OUT' do
+ let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::OPT_OUT }
+
+ context "when the feature flag hasn't been set" do
+ it 'returns true' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
+ end
+ end
+
+ context "when the feature flag is set to disable" do
+ before { Feature.get(real_feature_name).disable }
+
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/group_hierarchy_spec.rb
new file mode 100644
index 00000000000..5d0ed1522b3
--- /dev/null
+++ b/spec/lib/gitlab/group_hierarchy_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Gitlab::GroupHierarchy, :postgresql do
+ let!(:parent) { create(:group) }
+ let!(:child1) { create(:group, parent: parent) }
+ let!(:child2) { create(:group, parent: child1) }
+
+ describe '#base_and_ancestors' do
+ let(:relation) do
+ described_class.new(Group.where(id: child2.id)).base_and_ancestors
+ end
+
+ it 'includes the base rows' do
+ expect(relation).to include(child2)
+ end
+
+ it 'includes all of the ancestors' do
+ expect(relation).to include(parent, child1)
+ end
+ end
+
+ describe '#base_and_descendants' do
+ let(:relation) do
+ described_class.new(Group.where(id: parent.id)).base_and_descendants
+ end
+
+ it 'includes the base rows' do
+ expect(relation).to include(parent)
+ end
+
+ it 'includes all the descendants' do
+ expect(relation).to include(child1, child2)
+ end
+ end
+
+ describe '#all_groups' do
+ let(:relation) do
+ described_class.new(Group.where(id: child1.id)).all_groups
+ end
+
+ it 'includes the base rows' do
+ expect(relation).to include(child1)
+ end
+
+ it 'includes the ancestors' do
+ expect(relation).to include(parent)
+ end
+
+ it 'includes the descendants' do
+ expect(relation).to include(child2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index b9d4e59e770..3e0291c9ae9 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
describe Gitlab::ImportExport::MembersMapper, services: true do
describe 'map members' do
- let(:user) { create(:admin, authorized_projects_populated: true) }
+ let(:user) { create(:admin) }
let(:project) { create(:empty_project, :public, name: 'searchable_project') }
- let(:user2) { create(:user, authorized_projects_populated: true) }
+ let(:user2) { create(:user) }
let(:exported_user_id) { 99 }
let(:exported_members) do
[{
@@ -74,7 +74,7 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
end
context 'user is not an admin' do
- let(:user) { create(:user, authorized_projects_populated: true) }
+ let(:user) { create(:user) }
it 'does not map a project member' do
expect(members_mapper.map[exported_user_id]).to eq(user.id)
@@ -94,7 +94,7 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
end
context 'importer same as group member' do
- let(:user2) { create(:admin, authorized_projects_populated: true) }
+ let(:user2) { create(:admin) }
let(:group) { create(:group) }
let(:project) { create(:empty_project, :public, name: 'searchable_project', namespace: group) }
let(:members_mapper) do
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 8feb6c32f64..958784d8382 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -191,6 +191,7 @@ Ci::Pipeline:
- lock_version
- auto_canceled_by_id
- pipeline_schedule_id
+- source
CommitStatus:
- id
- project_id
diff --git a/spec/lib/gitlab/o_auth/provider_spec.rb b/spec/lib/gitlab/o_auth/provider_spec.rb
new file mode 100644
index 00000000000..1e2a1f8c039
--- /dev/null
+++ b/spec/lib/gitlab/o_auth/provider_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Gitlab::OAuth::Provider, lib: true do
+ describe '#config_for' do
+ context 'for an LDAP provider' do
+ context 'when the provider exists' do
+ it 'returns the config' do
+ expect(described_class.config_for('ldapmain')).to be_a(Hash)
+ end
+ end
+
+ context 'when the provider does not exist' do
+ it 'returns nil' do
+ expect(described_class.config_for('ldapfoo')).to be_nil
+ end
+ end
+ end
+
+ context 'for an OmniAuth provider' do
+ before do
+ provider = OpenStruct.new(
+ name: 'google',
+ app_id: 'asd123',
+ app_secret: 'asd123'
+ )
+ allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider])
+ end
+
+ context 'when the provider exists' do
+ it 'returns the config' do
+ expect(described_class.config_for('google')).to be_a(OpenStruct)
+ end
+ end
+
+ context 'when the provider does not exist' do
+ it 'returns nil' do
+ expect(described_class.config_for('foo')).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
new file mode 100644
index 00000000000..67321f43710
--- /dev/null
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Gitlab::ProjectAuthorizations do
+ let(:group) { create(:group) }
+ let!(:owned_project) { create(:empty_project) }
+ let!(:other_project) { create(:empty_project) }
+ let!(:group_project) { create(:empty_project, namespace: group) }
+
+ let(:user) { owned_project.namespace.owner }
+
+ def map_access_levels(rows)
+ rows.each_with_object({}) do |row, hash|
+ hash[row.project_id] = row.access_level
+ end
+ end
+
+ before do
+ other_project.team << [user, :reporter]
+ group.add_developer(user)
+ end
+
+ let(:authorizations) do
+ klass = if Group.supports_nested_groups?
+ Gitlab::ProjectAuthorizations::WithNestedGroups
+ else
+ Gitlab::ProjectAuthorizations::WithoutNestedGroups
+ end
+
+ klass.new(user).calculate
+ end
+
+ it 'returns the correct number of authorizations' do
+ expect(authorizations.length).to eq(3)
+ end
+
+ it 'includes the correct projects' do
+ expect(authorizations.pluck(:project_id)).
+ to include(owned_project.id, other_project.id, group_project.id)
+ end
+
+ it 'includes the correct access levels' do
+ mapping = map_access_levels(authorizations)
+
+ expect(mapping[owned_project.id]).to eq(Gitlab::Access::MASTER)
+ expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER)
+ expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
+ end
+
+ if Group.supports_nested_groups?
+ context 'with nested groups' do
+ let!(:nested_group) { create(:group, parent: group) }
+ let!(:nested_project) { create(:empty_project, namespace: nested_group) }
+
+ it 'includes nested groups' do
+ expect(authorizations.pluck(:project_id)).to include(nested_project.id)
+ end
+
+ it 'inherits access levels when the user is not a member of a nested group' do
+ mapping = map_access_levels(authorizations)
+
+ expect(mapping[nested_project.id]).to eq(Gitlab::Access::DEVELOPER)
+ end
+
+ it 'uses the greatest access level when a user is a member of a nested group' do
+ nested_group.add_master(user)
+
+ mapping = map_access_levels(authorizations)
+
+ expect(mapping[nested_project.id]).to eq(Gitlab::Access::MASTER)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 1b8690ba613..3d22784909d 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -123,8 +123,8 @@ describe Gitlab::ProjectSearchResults, lib: true do
context 'when wiki is internal' do
let(:project) { create(:project, :public, :wiki_private) }
- it 'finds wiki blobs for members' do
- project.add_reporter(user)
+ it 'finds wiki blobs for guest' do
+ project.add_guest(user)
is_expected.not_to be_empty
end
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
new file mode 100644
index 00000000000..25146860615
--- /dev/null
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::SQL::RecursiveCTE, :postgresql do
+ let(:cte) { described_class.new(:cte_name) }
+
+ describe '#to_arel' do
+ it 'generates an Arel relation for the CTE body' do
+ rel1 = User.where(id: 1)
+ rel2 = User.where(id: 2)
+
+ cte << rel1
+ cte << rel2
+
+ sql = cte.to_arel.to_sql
+ name = ActiveRecord::Base.connection.quote_table_name(:cte_name)
+
+ sql1, sql2 = ActiveRecord::Base.connection.unprepared_statement do
+ [rel1.except(:order).to_sql, rel2.except(:order).to_sql]
+ end
+
+ expect(sql).to eq("#{name} AS (#{sql1}\nUNION\n#{sql2})")
+ end
+ end
+
+ describe '#alias_to' do
+ it 'returns an alias for the CTE' do
+ table = Arel::Table.new(:kittens)
+
+ source_name = ActiveRecord::Base.connection.quote_table_name(:cte_name)
+ alias_name = ActiveRecord::Base.connection.quote_table_name(:kittens)
+
+ expect(cte.alias_to(table).to_sql).to eq("#{source_name} AS #{alias_name}")
+ end
+ end
+
+ describe '#apply_to' do
+ it 'applies a CTE to an ActiveRecord::Relation' do
+ user = create(:user)
+ cte = described_class.new(:cte_name)
+
+ cte << User.where(id: user.id)
+
+ relation = cte.apply_to(User.all)
+
+ expect(relation.to_sql).to match(/WITH RECURSIVE.+cte_name/)
+ expect(relation.to_a).to eq(User.where(id: user.id).to_a)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index fc144a2556a..6bce724a3f6 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -62,11 +62,6 @@ describe Gitlab::UrlSanitizer, lib: true do
end
end
- describe '.http_credentials_for_user' do
- it { expect(described_class.http_credentials_for_user(user)).to eq({ user: 'john.doe' }) }
- it { expect(described_class.http_credentials_for_user('foo')).to eq({}) }
- end
-
describe '#sanitized_url' do
it { expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git") }
end
@@ -76,7 +71,7 @@ describe Gitlab::UrlSanitizer, lib: true do
context 'when user is given to #initialize' do
let(:url_sanitizer) do
- described_class.new("https://github.com/me/project.git", credentials: described_class.http_credentials_for_user(user))
+ described_class.new("https://github.com/me/project.git", credentials: { user: user.username })
end
it { expect(url_sanitizer.credentials).to eq({ user: 'john.doe' }) }
@@ -94,7 +89,7 @@ describe Gitlab::UrlSanitizer, lib: true do
context 'when user is given to #initialize' do
let(:url_sanitizer) do
- described_class.new("https://github.com/me/project.git", credentials: described_class.http_credentials_for_user(user))
+ described_class.new("https://github.com/me/project.git", credentials: { user: user.username })
end
it { expect(url_sanitizer.full_url).to eq("https://john.doe@github.com/me/project.git") }
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index fdbb55fc874..b1999409170 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -244,7 +244,7 @@ describe Gitlab::Workhorse, lib: true do
context "when git_receive_pack action is passed" do
let(:action) { 'git_receive_pack' }
- it { expect(subject).not_to include(gitaly_params) }
+ it { expect(subject).to include(gitaly_params) }
end
context "when info_refs action is passed" do
diff --git a/spec/migrations/fill_authorized_projects_spec.rb b/spec/migrations/fill_authorized_projects_spec.rb
deleted file mode 100644
index 99dc4195818..00000000000
--- a/spec/migrations/fill_authorized_projects_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170106142508_fill_authorized_projects.rb')
-
-describe FillAuthorizedProjects do
- describe '#up' do
- it 'schedules the jobs in batches' do
- user1 = create(:user)
- user2 = create(:user)
-
- expect(Sidekiq::Client).to receive(:push_bulk).with(
- 'class' => 'AuthorizedProjectsWorker',
- 'args' => [[user1.id], [user2.id]]
- )
-
- described_class.new.up
- end
- end
-end
diff --git a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
new file mode 100644
index 00000000000..175bf1876b2
--- /dev/null
+++ b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb')
+
+describe TurnNestedGroupsIntoRegularGroupsForMysql do
+ let!(:parent_group) { create(:group) }
+ let!(:child_group) { create(:group, parent: parent_group) }
+ let!(:project) { create(:project, :empty_repo, namespace: child_group) }
+ let!(:member) { create(:user) }
+ let(:migration) { described_class.new }
+
+ before do
+ parent_group.add_developer(member)
+
+ allow(migration).to receive(:run_migration?).and_return(true)
+ allow(migration).to receive(:verbose).and_return(false)
+ end
+
+ describe '#up' do
+ let(:updated_project) do
+ # path_with_namespace is memoized in an instance variable so we retrieve a
+ # new row here to work around that.
+ Project.find(project.id)
+ end
+
+ before do
+ migration.up
+ end
+
+ it 'unsets the parent_id column' do
+ expect(Namespace.where('parent_id IS NOT NULL').any?).to eq(false)
+ end
+
+ it 'adds members of parent groups as members to the migrated group' do
+ is_member = child_group.members.
+ where(user_id: member, access_level: Gitlab::Access::DEVELOPER).any?
+
+ expect(is_member).to eq(true)
+ end
+
+ it 'update the path of the nested group' do
+ child_group.reload
+
+ expect(child_group.path).to eq("#{parent_group.name}-#{child_group.name}")
+ end
+
+ it 'renames projects of the nested group' do
+ expect(updated_project.path_with_namespace).
+ to eq("#{parent_group.name}-#{child_group.name}/#{updated_project.path}")
+ end
+
+ it 'renames the repository of any projects' do
+ expect(updated_project.repository.path).
+ to end_with("#{parent_group.name}-#{child_group.name}/#{updated_project.path}.git")
+
+ expect(File.directory?(updated_project.repository.path)).to eq(true)
+ end
+
+ it 'creates a redirect route for renamed projects' do
+ exists = RedirectRoute.
+ where(source_type: 'Project', source_id: project.id).
+ any?
+
+ expect(exists).to eq(true)
+ end
+ end
+end
diff --git a/spec/migrations/update_retried_for_ci_builds_spec.rb b/spec/migrations/update_retried_for_ci_build_spec.rb
index 3742b4dafe5..3742b4dafe5 100644
--- a/spec/migrations/update_retried_for_ci_builds_spec.rb
+++ b/spec/migrations/update_retried_for_ci_build_spec.rb
diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb
index 92fbf64a6b7..d56379eb59d 100644
--- a/spec/models/blob_viewer/base_spec.rb
+++ b/spec/models/blob_viewer/base_spec.rb
@@ -11,8 +11,8 @@ describe BlobViewer::Base, model: true do
self.extensions = %w(pdf)
self.binary = true
- self.overridable_max_size = 1.megabyte
- self.max_size = 5.megabytes
+ self.collapse_limit = 1.megabyte
+ self.size_limit = 5.megabytes
end
end
@@ -69,77 +69,49 @@ describe BlobViewer::Base, model: true do
end
end
- describe '#exceeds_overridable_max_size?' do
- context 'when the blob size is larger than the overridable max size' do
+ describe '#collapsed?' do
+ context 'when the blob size is larger than the collapse limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns true' do
- expect(viewer.exceeds_overridable_max_size?).to be_truthy
+ expect(viewer.collapsed?).to be_truthy
end
end
- context 'when the blob size is smaller than the overridable max size' do
+ context 'when the blob size is smaller than the collapse limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
it 'returns false' do
- expect(viewer.exceeds_overridable_max_size?).to be_falsey
+ expect(viewer.collapsed?).to be_falsey
end
end
end
- describe '#exceeds_max_size?' do
- context 'when the blob size is larger than the max size' do
+ describe '#too_large?' do
+ context 'when the blob size is larger than the size limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
it 'returns true' do
- expect(viewer.exceeds_max_size?).to be_truthy
+ expect(viewer.too_large?).to be_truthy
end
end
- context 'when the blob size is smaller than the max size' do
+ context 'when the blob size is smaller than the size limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns false' do
- expect(viewer.exceeds_max_size?).to be_falsey
- end
- end
- end
-
- describe '#can_override_max_size?' do
- context 'when the blob size is larger than the overridable max size' do
- context 'when the blob size is larger than the max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
-
- it 'returns false' do
- expect(viewer.can_override_max_size?).to be_falsey
- end
- end
-
- context 'when the blob size is smaller than the max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
-
- it 'returns true' do
- expect(viewer.can_override_max_size?).to be_truthy
- end
- end
- end
-
- context 'when the blob size is smaller than the overridable max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
-
- it 'returns false' do
- expect(viewer.can_override_max_size?).to be_falsey
+ expect(viewer.too_large?).to be_falsey
end
end
end
describe '#render_error' do
- context 'when the max size is overridden' do
+ context 'when expanded' do
before do
- viewer.override_max_size = true
+ viewer.expanded = true
end
- context 'when the blob size is larger than the max size' do
+ context 'when the blob size is larger than the size limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
it 'returns :too_large' do
@@ -147,7 +119,7 @@ describe BlobViewer::Base, model: true do
end
end
- context 'when the blob size is smaller than the max size' do
+ context 'when the blob size is smaller than the size limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns nil' do
@@ -156,16 +128,16 @@ describe BlobViewer::Base, model: true do
end
end
- context 'when the max size is not overridden' do
- context 'when the blob size is larger than the overridable max size' do
+ context 'when not expanded' do
+ context 'when the blob size is larger than the collapse limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
- it 'returns :too_large' do
- expect(viewer.render_error).to eq(:too_large)
+ it 'returns :collapsed' do
+ expect(viewer.render_error).to eq(:collapsed)
end
end
- context 'when the blob size is smaller than the overridable max size' do
+ context 'when the blob size is smaller than the collapse limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
it 'returns nil' do
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 822b98c5f6c..b00e7a73571 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -25,6 +25,14 @@ describe Ci::PipelineSchedule, models: true do
expect(pipeline_schedule).not_to be_valid
end
+
+ context 'when active is false' do
+ it 'does not allow nullified ref' do
+ pipeline_schedule = build(:ci_pipeline_schedule, :inactive, ref: nil)
+
+ expect(pipeline_schedule).not_to be_valid
+ end
+ end
end
describe '#set_next_run_at' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index c8023dc13b1..ae1b01b76ab 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -21,13 +21,35 @@ describe Ci::Pipeline, models: true do
it { is_expected.to have_many(:auto_canceled_pipelines) }
it { is_expected.to have_many(:auto_canceled_jobs) }
- it { is_expected.to validate_presence_of :sha }
- it { is_expected.to validate_presence_of :status }
+ it { is_expected.to validate_presence_of(:sha) }
+ it { is_expected.to validate_presence_of(:status) }
it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
+ describe '#source' do
+ context 'when creating new pipeline' do
+ let(:pipeline) do
+ build(:ci_empty_pipeline, status: :created, project: project, source: nil)
+ end
+
+ it "prevents from creating an object" do
+ expect(pipeline).not_to be_valid
+ end
+ end
+
+ context 'when updating existing pipeline' do
+ before do
+ pipeline.update_attribute(:source, nil)
+ end
+
+ it "object is valid" do
+ expect(pipeline).to be_valid
+ end
+ end
+ end
+
describe '#block' do
it 'changes pipeline status to manual' do
expect(pipeline.block).to be true
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 49a4132f763..0e10d91836d 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -115,123 +115,6 @@ describe Group, 'Routable' do
end
end
- describe '.member_descendants' do
- let!(:user) { create(:user) }
- let!(:nested_group) { create(:group, parent: group) }
-
- before { group.add_owner(user) }
- subject { described_class.member_descendants(user.id) }
-
- it { is_expected.to eq([nested_group]) }
- end
-
- describe '.member_self_and_descendants' do
- let!(:user) { create(:user) }
- let!(:nested_group) { create(:group, parent: group) }
-
- before { group.add_owner(user) }
- subject { described_class.member_self_and_descendants(user.id) }
-
- it { is_expected.to match_array [group, nested_group] }
- end
-
- describe '.member_hierarchy' do
- # foo/bar would also match foo/barbaz instead of just foo/bar and foo/bar/baz
- let!(:user) { create(:user) }
-
- # group
- # _______ (foo) _______
- # | |
- # | |
- # nested_group_1 nested_group_2
- # (bar) (barbaz)
- # | |
- # | |
- # nested_group_1_1 nested_group_2_1
- # (baz) (baz)
- #
- let!(:nested_group_1) { create :group, parent: group, name: 'bar' }
- let!(:nested_group_1_1) { create :group, parent: nested_group_1, name: 'baz' }
- let!(:nested_group_2) { create :group, parent: group, name: 'barbaz' }
- let!(:nested_group_2_1) { create :group, parent: nested_group_2, name: 'baz' }
-
- context 'user is not a member of any group' do
- subject { described_class.member_hierarchy(user.id) }
-
- it 'returns an empty array' do
- is_expected.to eq []
- end
- end
-
- context 'user is member of all groups' do
- before do
- group.add_owner(user)
- nested_group_1.add_owner(user)
- nested_group_1_1.add_owner(user)
- nested_group_2.add_owner(user)
- nested_group_2_1.add_owner(user)
- end
- subject { described_class.member_hierarchy(user.id) }
-
- it 'returns all groups' do
- is_expected.to match_array [
- group,
- nested_group_1, nested_group_1_1,
- nested_group_2, nested_group_2_1
- ]
- end
- end
-
- context 'user is member of the top group' do
- before { group.add_owner(user) }
- subject { described_class.member_hierarchy(user.id) }
-
- it 'returns all groups' do
- is_expected.to match_array [
- group,
- nested_group_1, nested_group_1_1,
- nested_group_2, nested_group_2_1
- ]
- end
- end
-
- context 'user is member of the first child (internal node), branch 1' do
- before { nested_group_1.add_owner(user) }
- subject { described_class.member_hierarchy(user.id) }
-
- it 'returns the groups in the hierarchy' do
- is_expected.to match_array [
- group,
- nested_group_1, nested_group_1_1
- ]
- end
- end
-
- context 'user is member of the first child (internal node), branch 2' do
- before { nested_group_2.add_owner(user) }
- subject { described_class.member_hierarchy(user.id) }
-
- it 'returns the groups in the hierarchy' do
- is_expected.to match_array [
- group,
- nested_group_2, nested_group_2_1
- ]
- end
- end
-
- context 'user is member of the last child (leaf node)' do
- before { nested_group_1_1.add_owner(user) }
- subject { described_class.member_hierarchy(user.id) }
-
- it 'returns the groups in the hierarchy' do
- is_expected.to match_array [
- group,
- nested_group_1, nested_group_1_1
- ]
- end
- end
- end
-
describe '#full_path' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 12519de8636..9fbe19b04d5 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -227,7 +227,10 @@ describe Environment, models: true do
context 'when user is allowed to stop environment' do
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
context 'when action did not yet finish' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 6ca1eb0374d..316bf153660 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -340,7 +340,7 @@ describe Group, models: true do
it { expect(subject.parent).to be_kind_of(Group) }
end
- describe '#members_with_parents' do
+ describe '#members_with_parents', :nested_groups do
let!(:group) { create(:group, :nested) }
let!(:master) { group.parent.add_user(create(:user), GroupMember::MASTER) }
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 87ea2e70680..cf9c701e8c5 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -22,16 +22,15 @@ describe ProjectMember, models: true do
end
describe '.add_user' do
- context 'when called with the project owner' do
- it 'adds the user as a member' do
- project = create(:empty_project)
+ it 'adds the user as a member' do
+ user = create(:user)
+ project = create(:empty_project)
- expect(project.users).not_to include(project.owner)
+ expect(project.users).not_to include(user)
- described_class.add_user(project, project.owner, :master, current_user: project.owner)
+ described_class.add_user(project, user, :master, current_user: project.owner)
- expect(project.users.reload).to include(project.owner)
- end
+ expect(project.users.reload).to include(user)
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 712470d6bf5..58f273ade28 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -238,10 +238,10 @@ describe MergeRequest, models: true do
end
context 'when there are no MR diffs' do
- it 'delegates to the compare object, setting no_collapse: true' do
+ it 'delegates to the compare object, setting expanded: true' do
merge_request.compare = double(:compare)
- expect(merge_request.compare).to receive(:diffs).with(options.merge(no_collapse: true))
+ expect(merge_request.compare).to receive(:diffs).with(options.merge(expanded: true))
merge_request.diffs(options)
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index e3e8e6d571c..aa1ce89ffd7 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -249,4 +249,17 @@ describe Milestone, models: true do
expect(milestone.to_reference(another_project)).to eq "sample-project%1"
end
end
+
+ describe '#participants' do
+ let(:project) { build(:empty_project, name: 'sample-project') }
+ let(:milestone) { build(:milestone, iid: 1, project: project) }
+
+ it 'returns participants without duplicates' do
+ user = create :user
+ create :issue, project: project, milestone: milestone, assignees: [user]
+ create :issue, project: project, milestone: milestone, assignees: [user]
+
+ expect(milestone.participants).to eq [user]
+ end
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index ff5e7c350aa..0e74f1ab1bd 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -287,21 +287,21 @@ describe Namespace, models: true do
end
end
- describe '#ancestors' do
+ describe '#ancestors', :nested_groups do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
it 'returns the correct ancestors' do
- expect(very_deep_nested_group.ancestors).to eq([group, nested_group, deep_nested_group])
- expect(deep_nested_group.ancestors).to eq([group, nested_group])
- expect(nested_group.ancestors).to eq([group])
+ expect(very_deep_nested_group.ancestors).to include(group, nested_group, deep_nested_group)
+ expect(deep_nested_group.ancestors).to include(group, nested_group)
+ expect(nested_group.ancestors).to include(group)
expect(group.ancestors).to eq([])
end
end
- describe '#descendants' do
+ describe '#descendants', :nested_groups do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -311,9 +311,9 @@ describe Namespace, models: true do
it 'returns the correct descendants' do
expect(very_deep_nested_group.descendants.to_a).to eq([])
- expect(deep_nested_group.descendants.to_a).to eq([very_deep_nested_group])
- expect(nested_group.descendants.to_a).to eq([deep_nested_group, very_deep_nested_group])
- expect(group.descendants.to_a).to eq([nested_group, deep_nested_group, very_deep_nested_group])
+ expect(deep_nested_group.descendants.to_a).to include(very_deep_nested_group)
+ expect(nested_group.descendants.to_a).to include(deep_nested_group, very_deep_nested_group)
+ expect(group.descendants.to_a).to include(nested_group, deep_nested_group, very_deep_nested_group)
end
end
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 9b711bfc007..4161b9158b1 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -23,7 +23,7 @@ describe ProjectGroupLink do
expect(project_group_link).not_to be_valid
end
- it "doesn't allow a project to be shared with an ancestor of the group it is in" do
+ it "doesn't allow a project to be shared with an ancestor of the group it is in", :nested_groups do
project_group_link.group = parent_group
expect(project_group_link).not_to be_valid
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 349067e73ab..1920b5bf42b 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -133,6 +133,7 @@ describe JiraService, models: true do
allow(JIRA::Resource::Issue).to receive(:find).and_return(open_issue, closed_issue)
allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return("JIRA-123")
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
@jira_service.save
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f2b4e9070b4..da1b29a2bda 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -948,6 +948,20 @@ describe Project, models: true do
end
end
+ describe '.starred_by' do
+ it 'returns only projects starred by the given user' do
+ user1 = create(:user)
+ user2 = create(:user)
+ project1 = create(:empty_project)
+ project2 = create(:empty_project)
+ create(:empty_project)
+ user1.toggle_star(project1)
+ user2.toggle_star(project2)
+
+ expect(Project.starred_by(user1)).to contain_exactly(project1)
+ end
+ end
+
describe '.visible_to_user' do
let!(:project) { create(:empty_project, :private) }
let!(:user) { create(:user) }
@@ -1431,6 +1445,31 @@ describe Project, models: true do
end
end
+ describe 'Project import job' do
+ let(:project) { create(:empty_project) }
+ let(:mirror) { false }
+
+ before do
+ allow_any_instance_of(Gitlab::Shell).to receive(:import_repository)
+ .with(project.repository_storage_path, project.path_with_namespace, project.import_url)
+ .and_return(true)
+
+ allow(project).to receive(:repository_exists?).and_return(true)
+
+ expect_any_instance_of(Repository).to receive(:after_import)
+ .and_call_original
+ end
+
+ it 'imports a project' do
+ expect_any_instance_of(RepositoryImportWorker).to receive(:perform).and_call_original
+
+ project.import_start
+ project.add_import_job
+
+ expect(project.reload.import_status).to eq('finished')
+ end
+ end
+
describe '#latest_successful_builds_for' do
def create_pipeline(status = 'success')
create(:ci_pipeline, project: project,
@@ -1884,19 +1923,9 @@ describe Project, models: true do
describe '#http_url_to_repo' do
let(:project) { create :empty_project }
- context 'when no user is given' do
- it 'returns the url to the repo without a username' do
- expect(project.http_url_to_repo).to eq("#{project.web_url}.git")
- expect(project.http_url_to_repo).not_to include('@')
- end
- end
-
- context 'when user is given' do
- it 'returns the url to the repo with the username' do
- user = build_stubbed(:user)
-
- expect(project.http_url_to_repo(user)).to start_with("http://#{user.username}@")
- end
+ it 'returns the url to the repo without a username' do
+ expect(project.http_url_to_repo).to eq("#{project.web_url}.git")
+ expect(project.http_url_to_repo).not_to include('@')
end
end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 942eeab251d..fb2d5f60009 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -81,7 +81,7 @@ describe ProjectTeam, models: true do
user = create(:user)
project.add_guest(user)
- expect(project.team.members).to contain_exactly(user)
+ expect(project.team.members).to contain_exactly(user, project.owner)
end
it 'returns project members of a specified level' do
@@ -100,7 +100,8 @@ describe ProjectTeam, models: true do
group_access: Gitlab::Access::GUEST
)
- expect(project.team.members).to contain_exactly(group_member.user)
+ expect(project.team.members).
+ to contain_exactly(group_member.user, project.owner)
end
it 'returns invited members of a group of a specified level' do
@@ -137,7 +138,10 @@ describe ProjectTeam, models: true do
describe '#find_member' do
context 'personal project' do
- let(:project) { create(:empty_project, :public, :access_requestable) }
+ let(:project) do
+ create(:empty_project, :public, :access_requestable)
+ end
+
let(:requester) { create(:user) }
before do
@@ -200,7 +204,9 @@ describe ProjectTeam, models: true do
let(:requester) { create(:user) }
context 'personal project' do
- let(:project) { create(:empty_project, :public, :access_requestable) }
+ let(:project) do
+ create(:empty_project, :public, :access_requestable)
+ end
context 'when project is not shared with group' do
before do
@@ -244,7 +250,9 @@ describe ProjectTeam, models: true do
context 'group project' do
let(:group) { create(:group, :access_requestable) }
- let!(:project) { create(:empty_project, group: group) }
+ let!(:project) do
+ create(:empty_project, group: group)
+ end
before do
group.add_master(master)
@@ -265,8 +273,15 @@ describe ProjectTeam, models: true do
let(:group) { create(:group) }
let(:developer) { create(:user) }
let(:master) { create(:user) }
- let(:personal_project) { create(:empty_project, namespace: developer.namespace) }
- let(:group_project) { create(:empty_project, namespace: group) }
+
+ let(:personal_project) do
+ create(:empty_project, namespace: developer.namespace)
+ end
+
+ let(:group_project) do
+ create(:empty_project, namespace: group)
+ end
+
let(:members_project) { create(:empty_project) }
let(:shared_project) { create(:empty_project) }
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 969e9f7a130..224067f58dd 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -37,21 +37,11 @@ describe ProjectWiki, models: true do
describe "#http_url_to_repo" do
let(:project) { create :empty_project }
- context 'when no user is given' do
- it 'returns the url to the repo without a username' do
- expected_url = "#{Gitlab.config.gitlab.url}/#{subject.path_with_namespace}.git"
+ it 'returns the full http url to the repo' do
+ expected_url = "#{Gitlab.config.gitlab.url}/#{subject.path_with_namespace}.git"
- expect(project_wiki.http_url_to_repo).to eq(expected_url)
- expect(project_wiki.http_url_to_repo).not_to include('@')
- end
- end
-
- context 'when user is given' do
- it 'returns the url to the repo with the username' do
- user = build_stubbed(:user)
-
- expect(project_wiki.http_url_to_repo(user)).to start_with("http://#{user.username}@")
- end
+ expect(project_wiki.http_url_to_repo).to eq(expected_url)
+ expect(project_wiki.http_url_to_repo).not_to include('@')
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index aabdac4bb75..fe9df3360ff 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -627,16 +627,6 @@ describe User, models: true do
it { expect(User.without_projects).to include user_without_project2 }
end
- describe '.not_in_project' do
- before do
- User.delete_all
- @user = create :user
- @project = create(:empty_project)
- end
-
- it { expect(User.not_in_project(@project)).to include(@user, @project.owner) }
- end
-
describe 'user creation' do
describe 'normal user' do
let(:user) { create(:user, name: 'John Smith') }
@@ -1506,25 +1496,6 @@ describe User, models: true do
end
end
- describe '#viewable_starred_projects' do
- let(:user) { create(:user) }
- let(:public_project) { create(:empty_project, :public) }
- let(:private_project) { create(:empty_project, :private) }
- let(:private_viewable_project) { create(:empty_project, :private) }
-
- before do
- private_viewable_project.team << [user, Gitlab::Access::MASTER]
-
- [public_project, private_project, private_viewable_project].each do |project|
- user.toggle_star(project)
- end
- end
-
- it 'returns only starred projects the user can view' do
- expect(user.viewable_starred_projects).not_to include(private_project)
- end
- end
-
describe '#projects_with_reporter_access_limited_to' do
let(:project1) { create(:empty_project) }
let(:project2) { create(:empty_project) }
@@ -1561,48 +1532,103 @@ describe User, models: true do
end
end
- describe '#nested_groups' do
+ describe '#all_expanded_groups' do
+ # foo/bar would also match foo/barbaz instead of just foo/bar and foo/bar/baz
let!(:user) { create(:user) }
- let!(:group) { create(:group) }
- let!(:nested_group) { create(:group, parent: group) }
- before do
- group.add_owner(user)
+ # group
+ # _______ (foo) _______
+ # | |
+ # | |
+ # nested_group_1 nested_group_2
+ # (bar) (barbaz)
+ # | |
+ # | |
+ # nested_group_1_1 nested_group_2_1
+ # (baz) (baz)
+ #
+ let!(:group) { create :group }
+ let!(:nested_group_1) { create :group, parent: group, name: 'bar' }
+ let!(:nested_group_1_1) { create :group, parent: nested_group_1, name: 'baz' }
+ let!(:nested_group_2) { create :group, parent: group, name: 'barbaz' }
+ let!(:nested_group_2_1) { create :group, parent: nested_group_2, name: 'baz' }
+
+ subject { user.all_expanded_groups }
- # Add more data to ensure method does not include wrong groups
- create(:group).add_owner(create(:user))
+ context 'user is not a member of any group' do
+ it 'returns an empty array' do
+ is_expected.to eq([])
+ end
end
- it { expect(user.nested_groups).to eq([nested_group]) }
- end
+ context 'user is member of all groups' do
+ before do
+ group.add_owner(user)
+ nested_group_1.add_owner(user)
+ nested_group_1_1.add_owner(user)
+ nested_group_2.add_owner(user)
+ nested_group_2_1.add_owner(user)
+ end
- describe '#all_expanded_groups' do
- let!(:user) { create(:user) }
- let!(:group) { create(:group) }
- let!(:nested_group_1) { create(:group, parent: group) }
- let!(:nested_group_2) { create(:group, parent: group) }
+ it 'returns all groups' do
+ is_expected.to match_array [
+ group,
+ nested_group_1, nested_group_1_1,
+ nested_group_2, nested_group_2_1
+ ]
+ end
+ end
- before { nested_group_1.add_owner(user) }
+ context 'user is member of the top group' do
+ before { group.add_owner(user) }
- it { expect(user.all_expanded_groups).to match_array [group, nested_group_1] }
- end
+ if Group.supports_nested_groups?
+ it 'returns all groups' do
+ is_expected.to match_array [
+ group,
+ nested_group_1, nested_group_1_1,
+ nested_group_2, nested_group_2_1
+ ]
+ end
+ else
+ it 'returns the top-level groups' do
+ is_expected.to match_array [group]
+ end
+ end
+ end
- describe '#nested_groups_projects' do
- let!(:user) { create(:user) }
- let!(:group) { create(:group) }
- let!(:nested_group) { create(:group, parent: group) }
- let!(:project) { create(:empty_project, namespace: group) }
- let!(:nested_project) { create(:empty_project, namespace: nested_group) }
+ context 'user is member of the first child (internal node), branch 1', :nested_groups do
+ before { nested_group_1.add_owner(user) }
- before do
- group.add_owner(user)
+ it 'returns the groups in the hierarchy' do
+ is_expected.to match_array [
+ group,
+ nested_group_1, nested_group_1_1
+ ]
+ end
+ end
+
+ context 'user is member of the first child (internal node), branch 2', :nested_groups do
+ before { nested_group_2.add_owner(user) }
- # Add more data to ensure method does not include wrong projects
- other_project = create(:empty_project, namespace: create(:group, :nested))
- other_project.add_developer(create(:user))
+ it 'returns the groups in the hierarchy' do
+ is_expected.to match_array [
+ group,
+ nested_group_2, nested_group_2_1
+ ]
+ end
end
- it { expect(user.nested_groups_projects).to eq([nested_project]) }
+ context 'user is member of the last child (leaf node)', :nested_groups do
+ before { nested_group_1_1.add_owner(user) }
+
+ it 'returns the groups in the hierarchy' do
+ is_expected.to match_array [
+ group,
+ nested_group_1, nested_group_1_1
+ ]
+ end
+ end
end
describe '#refresh_authorized_projects', redis: true do
@@ -1622,10 +1648,6 @@ describe User, models: true do
expect(user.project_authorizations.count).to eq(2)
end
- it 'sets the authorized_projects_populated column' do
- expect(user.authorized_projects_populated).to eq(true)
- end
-
it 'stores the correct access levels' do
expect(user.project_authorizations.where(access_level: Gitlab::Access::GUEST).exists?).to eq(true)
expect(user.project_authorizations.where(access_level: Gitlab::Access::REPORTER).exists?).to eq(true)
@@ -1735,7 +1757,7 @@ describe User, models: true do
end
end
- context 'with 2FA requirement on nested parent group' do
+ context 'with 2FA requirement on nested parent group', :nested_groups do
let!(:group1) { create :group, require_two_factor_authentication: true }
let!(:group1a) { create :group, require_two_factor_authentication: false, parent: group1 }
@@ -1750,7 +1772,7 @@ describe User, models: true do
end
end
- context 'with 2FA requirement on nested child group' do
+ context 'with 2FA requirement on nested child group', :nested_groups do
let!(:group1) { create :group, require_two_factor_authentication: false }
let!(:group1a) { create :group, require_two_factor_authentication: true, parent: group1 }
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 2077c14ff7a..4c37a553227 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -107,7 +107,7 @@ describe GroupPolicy, models: true do
end
end
- describe 'private nested group inherit permissions' do
+ describe 'private nested group inherit permissions', :nested_groups do
let(:nested_group) { create(:group, :private, parent: group) }
subject { described_class.abilities(current_user, nested_group).to_set }
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 1c163cee152..6b637a03b6f 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -16,8 +16,8 @@ describe API::CommitStatuses do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do
- let!(:master) { project.pipelines.create(sha: commit.id, ref: 'master') }
- let!(:develop) { project.pipelines.create(sha: commit.id, ref: 'develop') }
+ let!(:master) { project.pipelines.create(source: :push, sha: commit.id, ref: 'master') }
+ let!(:develop) { project.pipelines.create(source: :push, sha: commit.id, ref: 'develop') }
context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 0b0e4c2b112..b0c265b6453 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -5,7 +5,6 @@ describe API::Commits do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
- let!(:master) { create(:project_member, :master, user: user, project: project) }
let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
@@ -486,7 +485,7 @@ describe API::Commits do
end
it "returns status for CI" do
- pipeline = project.ensure_pipeline('master', project.repository.commit.sha)
+ pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
pipeline.update(status: 'success')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
@@ -496,7 +495,7 @@ describe API::Commits do
end
it "returns status for CI when pipeline is created" do
- project.ensure_pipeline('master', project.repository.commit.sha)
+ project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
new file mode 100644
index 00000000000..f169e6661d1
--- /dev/null
+++ b/spec/requests/api/features_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe API::Features do
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+
+ describe 'GET /features' do
+ let(:expected_features) do
+ [
+ {
+ 'name' => 'feature_1',
+ 'state' => 'on',
+ 'gates' => [{ 'key' => 'boolean', 'value' => true }]
+ },
+ {
+ 'name' => 'feature_2',
+ 'state' => 'off',
+ 'gates' => [{ 'key' => 'boolean', 'value' => false }]
+ }
+ ]
+ end
+
+ before do
+ Feature.get('feature_1').enable
+ Feature.get('feature_2').disable
+ end
+
+ it 'returns a 401 for anonymous users' do
+ get api('/features')
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns a 403 for users' do
+ get api('/features', user)
+
+ expect(response).to have_http_status(403)
+ end
+
+ it 'returns the feature list for admins' do
+ get api('/features', admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to match_array(expected_features)
+ end
+ end
+
+ describe 'POST /feature' do
+ let(:feature_name) { 'my_feature' }
+ it 'returns a 401 for anonymous users' do
+ post api("/features/#{feature_name}")
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns a 403 for users' do
+ post api("/features/#{feature_name}", user)
+
+ expect(response).to have_http_status(403)
+ end
+
+ it 'creates an enabled feature if passed true' do
+ post api("/features/#{feature_name}", admin), value: 'true'
+
+ expect(response).to have_http_status(201)
+ expect(Feature.get(feature_name)).to be_enabled
+ end
+
+ it 'creates a feature with the given percentage if passed an integer' do
+ post api("/features/#{feature_name}", admin), value: '50'
+
+ expect(response).to have_http_status(201)
+ expect(Feature.get(feature_name).percentage_of_time_value).to be(50)
+ end
+
+ context 'when the feature exists' do
+ let(:feature) { Feature.get(feature_name) }
+
+ before do
+ feature.disable # This also persists the feature on the DB
+ end
+
+ it 'enables the feature if passed true' do
+ post api("/features/#{feature_name}", admin), value: 'true'
+
+ expect(response).to have_http_status(201)
+ expect(feature).to be_enabled
+ end
+
+ context 'with a pre-existing percentage value' do
+ before do
+ feature.enable_percentage_of_time(50)
+ end
+
+ it 'updates the percentage of time if passed an integer' do
+ post api("/features/#{feature_name}", admin), value: '30'
+
+ expect(response).to have_http_status(201)
+ expect(Feature.get(feature_name).percentage_of_time_value).to be(30)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 90b36374ded..bb53796cbd7 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -429,7 +429,7 @@ describe API::Groups do
expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled])
end
- it "creates a nested group" do
+ it "creates a nested group", :nested_groups do
parent = create(:group)
parent.add_owner(user3)
group = attributes_for(:group, { parent_id: parent.id })
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
new file mode 100644
index 00000000000..85d11deb26f
--- /dev/null
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -0,0 +1,297 @@
+require 'spec_helper'
+
+describe API::PipelineSchedules do
+ set(:developer) { create(:user) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+
+ before do
+ project.add_developer(developer)
+ end
+
+ describe 'GET /projects/:id/pipeline_schedules' do
+ context 'authenticated user with valid permissions' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
+
+ before do
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ it 'returns list of pipeline_schedules' do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response).to match_response_schema('pipeline_schedules')
+ end
+
+ it 'avoids N + 1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+ end.count
+
+ create_list(:ci_pipeline_schedule, 10, project: project)
+ .each do |pipeline_schedule|
+ create(:user).tap do |user|
+ project.add_developer(user)
+ pipeline_schedule.update_attributes(owner: user)
+ end
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ expect do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+ end.not_to exceed_query_limit(control_count)
+ end
+
+ %w[active inactive].each do |target|
+ context "when scope is #{target}" do
+ before do
+ create(:ci_pipeline_schedule, project: project, active: active?(target))
+ end
+
+ it 'returns matched pipeline schedules' do
+ get api("/projects/#{project.id}/pipeline_schedules", developer), scope: target
+
+ expect(json_response.map{ |r| r['active'] }).to all(eq(active?(target)))
+ end
+ end
+
+ def active?(str)
+ (str == 'active') ? true : false
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
+
+ before do
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'returns pipeline_schedule details' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
+ get api("/projects/#{project.id}/pipeline_schedules/-5", developer)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules' do
+ let(:params) { attributes_for(:ci_pipeline_schedule) }
+
+ context 'authenticated user with valid permissions' do
+ context 'with required parameters' do
+ it 'creates pipeline_schedule' do
+ expect do
+ post api("/projects/#{project.id}/pipeline_schedules", developer),
+ params
+ end.to change { project.pipeline_schedules.count }.by(1)
+
+ expect(response).to have_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule')
+ expect(json_response['description']).to eq(params[:description])
+ expect(json_response['ref']).to eq(params[:ref])
+ expect(json_response['cron']).to eq(params[:cron])
+ expect(json_response['cron_timezone']).to eq(params[:cron_timezone])
+ expect(json_response['owner']['id']).to eq(developer.id)
+ end
+ end
+
+ context 'without required parameters' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", developer)
+
+ expect(response).to have_http_status(:bad_request)
+ end
+ end
+
+ context 'when cron has validation error' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", developer),
+ params.merge('cron' => 'invalid-cron')
+
+ expect(response).to have_http_status(:bad_request)
+ expect(json_response['message']).to have_key('cron')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", user), params
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules"), params
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates cron' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
+ cron: '1 2 3 4 *'
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule')
+ expect(json_response['cron']).to eq('1 2 3 4 *')
+ end
+
+ context 'when cron has validation error' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
+ cron: 'invalid-cron'
+
+ expect(response).to have_http_status(:bad_request)
+ expect(json_response['message']).to have_key('cron')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
+ let(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer)
+
+ expect(response).to have_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:master) { create(:user) }
+
+ let!(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ before do
+ project.add_master(master)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'deletes pipeline_schedule' do
+ expect do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", master)
+ end.to change { project.pipeline_schedules.count }.by(-1)
+
+ expect(response).to have_http_status(:accepted)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/-5", master)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not delete pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
+
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not delete pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index f9e5316b3de..9e6957e9922 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -7,7 +7,7 @@ describe API::Pipelines do
let!(:pipeline) do
create(:ci_empty_pipeline, project: project, sha: project.commit.id,
- ref: project.default_branch)
+ ref: project.default_branch, user: user)
end
before { project.team << [user, :master] }
@@ -232,20 +232,26 @@ describe API::Pipelines do
context 'when order_by and sort are specified' do
context 'when order_by user_id' do
- let!(:pipeline) { create_list(:ci_pipeline, 2, project: project, user: create(:user)) }
+ before do
+ 3.times do
+ create(:ci_pipeline, project: project, user: create(:user))
+ end
+ end
- it 'sorts as user_id: :asc' do
- get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'asc'
+ context 'when sort parameter is valid' do
+ it 'sorts as user_id: :desc' do
+ get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'desc'
- expect(response).to have_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).not_to be_empty
- pipeline.sort_by { |p| p.user.id }.tap do |sorted_pipeline|
- json_response.each_with_index { |r, i| expect(r['id']).to eq(sorted_pipeline[i].id) }
+ expect(response).to have_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).not_to be_empty
+
+ pipeline_ids = Ci::Pipeline.all.order(user_id: :desc).pluck(:id)
+ expect(json_response.map { |r| r['id'] }).to eq(pipeline_ids)
end
end
- context 'when sort is invalid' do
+ context 'when sort parameter is invalid' do
it 'returns bad_request' do
get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'invalid_sort'
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index d5c3b5b34ad..3d98551628b 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -11,8 +11,7 @@ describe API::Projects do
let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
let(:project2) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
- let(:project_member) { create(:project_member, :master, user: user, project: project) }
- let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
+ let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
let(:project3) do
create(:project,
@@ -27,7 +26,7 @@ describe API::Projects do
builds_enabled: false,
snippets_enabled: false)
end
- let(:project_member3) do
+ let(:project_member2) do
create(:project_member,
user: user4,
project: project3,
@@ -210,7 +209,7 @@ describe API::Projects do
let(:public_project) { create(:empty_project, :public) }
before do
- project_member2
+ project_member
user3.update_attributes(starred_projects: [project, project2, project3, public_project])
end
@@ -391,6 +390,14 @@ describe API::Projects do
expect(json_response['visibility']).to eq('private')
end
+ it 'sets tag list to a project' do
+ project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])
+
+ post api('/projects', user), project
+
+ expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond])
+ end
+
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
post api('/projects', user), project
@@ -784,19 +791,18 @@ describe API::Projects do
describe 'GET /projects/:id/users' do
shared_examples_for 'project users response' do
it 'returns the project users' do
- member = create(:user)
- create(:project_member, :developer, user: member, project: project)
-
get api("/projects/#{project.id}/users", current_user)
+ user = project.namespace.owner
+
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
first_user = json_response.first
- expect(first_user['username']).to eq(member.username)
- expect(first_user['name']).to eq(member.name)
+ expect(first_user['username']).to eq(user.username)
+ expect(first_user['name']).to eq(user.name)
expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url])
end
end
@@ -1091,8 +1097,8 @@ describe API::Projects do
before { user4 }
before { project3 }
before { project4 }
- before { project_member3 }
before { project_member2 }
+ before { project_member }
it 'returns 400 when nothing sent' do
project_param = {}
@@ -1573,7 +1579,7 @@ describe API::Projects do
context 'when authenticated as developer' do
before do
- project_member2
+ project_member
end
it 'returns forbidden error' do
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index c2e8c3ae6f7..4a4a5dc5c7c 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -5,7 +5,6 @@ describe API::V3::Commits do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
- let!(:master) { create(:project_member, :master, user: user, project: project) }
let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
@@ -387,7 +386,7 @@ describe API::V3::Commits do
end
it "returns status for CI" do
- pipeline = project.ensure_pipeline('master', project.repository.commit.sha)
+ pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
pipeline.update(status: 'success')
get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
@@ -397,7 +396,7 @@ describe API::V3::Commits do
end
it "returns status for CI when pipeline is created" do
- project.ensure_pipeline('master', project.repository.commit.sha)
+ project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb
index bc261b5e07c..98e8c954909 100644
--- a/spec/requests/api/v3/groups_spec.rb
+++ b/spec/requests/api/v3/groups_spec.rb
@@ -421,7 +421,7 @@ describe API::V3::Groups do
expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled])
end
- it "creates a nested group" do
+ it "creates a nested group", :nested_groups do
parent = create(:group)
parent.add_owner(user3)
group = attributes_for(:group, { parent_id: parent.id })
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index dc7c3d125b1..bc591b2eb37 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -10,8 +10,7 @@ describe API::V3::Projects do
let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
let(:project2) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
- let(:project_member) { create(:project_member, :master, user: user, project: project) }
- let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
+ let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
let(:project3) do
create(:project,
@@ -25,7 +24,7 @@ describe API::V3::Projects do
issues_enabled: false, wiki_enabled: false,
snippets_enabled: false)
end
- let(:project_member3) do
+ let(:project_member2) do
create(:project_member,
user: user4,
project: project3,
@@ -286,7 +285,7 @@ describe API::V3::Projects do
let(:public_project) { create(:empty_project, :public) }
before do
- project_member2
+ project_member
user3.update_attributes(starred_projects: [project, project2, project3, public_project])
end
@@ -622,7 +621,6 @@ describe API::V3::Projects do
context 'when authenticated' do
before do
project
- project_member
end
it 'returns a project by id' do
@@ -814,8 +812,7 @@ describe API::V3::Projects do
describe 'GET /projects/:id/users' do
shared_examples_for 'project users response' do
it 'returns the project users' do
- member = create(:user)
- create(:project_member, :developer, user: member, project: project)
+ member = project.owner
get v3_api("/projects/#{project.id}/users", current_user)
@@ -1163,8 +1160,8 @@ describe API::V3::Projects do
before { user4 }
before { project3 }
before { project4 }
- before { project_member3 }
before { project_member2 }
+ before { project_member }
context 'when unauthenticated' do
it 'returns authentication error' do
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
new file mode 100644
index 00000000000..968dcd6232e
--- /dev/null
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/migration/update_column_in_batches'
+
+describe RuboCop::Cop::Migration::UpdateColumnInBatches do
+ let(:cop) { described_class.new }
+ let(:tmp_rails_root) { Rails.root.join('tmp', 'rails_root') }
+ let(:migration_code) do
+ <<-END
+ def up
+ update_column_in_batches(:projects, :name, "foo") do |table, query|
+ query.where(table[:name].eq(nil))
+ end
+ end
+ END
+ end
+
+ before do
+ allow(cop).to receive(:rails_root).and_return(tmp_rails_root)
+ end
+ after do
+ FileUtils.rm_rf(tmp_rails_root)
+ end
+
+ context 'outside of a migration' do
+ it 'does not register any offenses' do
+ inspect_source(cop, migration_code)
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ let(:spec_filepath) { tmp_rails_root.join('spec', 'migrations', 'my_super_migration_spec.rb') }
+
+ shared_context 'with a migration file' do
+ before do
+ FileUtils.mkdir_p(File.dirname(migration_filepath))
+ @migration_file = File.new(migration_filepath, 'w+')
+ end
+ after do
+ @migration_file.close
+ end
+ end
+
+ shared_examples 'a migration file with no spec file' do
+ include_context 'with a migration file'
+
+ let(:relative_spec_filepath) { Pathname.new(spec_filepath).relative_path_from(tmp_rails_root) }
+
+ it 'registers an offense when using update_column_in_batches' do
+ inspect_source(cop, migration_code, @migration_file)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([2])
+ expect(cop.offenses.first.message).
+ to include("`#{relative_spec_filepath}`")
+ end
+ end
+ end
+
+ shared_examples 'a migration file with a spec file' do
+ include_context 'with a migration file'
+
+ before do
+ FileUtils.mkdir_p(File.dirname(spec_filepath))
+ @spec_file = File.new(spec_filepath, 'w+')
+ end
+ after do
+ @spec_file.close
+ end
+
+ it 'does not register any offenses' do
+ inspect_source(cop, migration_code, @migration_file)
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'in a migration' do
+ let(:migration_filepath) { tmp_rails_root.join('db', 'migrate', '20121220064453_my_super_migration.rb') }
+
+ it_behaves_like 'a migration file with no spec file'
+ it_behaves_like 'a migration file with a spec file'
+ end
+
+ context 'in a post migration' do
+ let(:migration_filepath) { tmp_rails_root.join('db', 'post_migrate', '20121220064453_my_super_migration.rb') }
+
+ it_behaves_like 'a migration file with no spec file'
+ it_behaves_like 'a migration file with a spec file'
+ end
+end
diff --git a/spec/serializers/build_action_entity_spec.rb b/spec/serializers/build_action_entity_spec.rb
index 059deba5416..15720d86583 100644
--- a/spec/serializers/build_action_entity_spec.rb
+++ b/spec/serializers/build_action_entity_spec.rb
@@ -1,26 +1,26 @@
require 'spec_helper'
describe BuildActionEntity do
- let(:build) { create(:ci_build, name: 'test_build') }
+ let(:job) { create(:ci_build, name: 'test_job') }
let(:request) { double('request') }
let(:entity) do
- described_class.new(build, request: spy('request'))
+ described_class.new(job, request: spy('request'))
end
describe '#as_json' do
subject { entity.as_json }
- it 'contains original build name' do
- expect(subject[:name]).to eq 'test_build'
+ it 'contains original job name' do
+ expect(subject[:name]).to eq 'test_job'
end
it 'contains path to the action play' do
- expect(subject[:path]).to include "builds/#{build.id}/play"
+ expect(subject[:path]).to include "jobs/#{job.id}/play"
end
it 'contains whether it is playable' do
- expect(subject[:playable]).to eq build.playable?
+ expect(subject[:playable]).to eq job.playable?
end
end
end
diff --git a/spec/serializers/build_artifact_entity_spec.rb b/spec/serializers/build_artifact_entity_spec.rb
index 2fc60aa9de6..b4eef20d6a6 100644
--- a/spec/serializers/build_artifact_entity_spec.rb
+++ b/spec/serializers/build_artifact_entity_spec.rb
@@ -1,22 +1,22 @@
require 'spec_helper'
describe BuildArtifactEntity do
- let(:build) { create(:ci_build, name: 'test:build') }
+ let(:job) { create(:ci_build, name: 'test:job') }
let(:entity) do
- described_class.new(build, request: double)
+ described_class.new(job, request: double)
end
describe '#as_json' do
subject { entity.as_json }
- it 'contains build name' do
- expect(subject[:name]).to eq 'test:build'
+ it 'contains job name' do
+ expect(subject[:name]).to eq 'test:job'
end
it 'contains path to the artifacts' do
expect(subject[:path])
- .to include "builds/#{build.id}/artifacts/download"
+ .to include "jobs/#{job.id}/artifacts/download"
end
end
end
diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb
index b5eb84ae43b..6d5e1046e86 100644
--- a/spec/serializers/build_entity_spec.rb
+++ b/spec/serializers/build_entity_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe BuildEntity do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
+ let(:project) { build.project }
let(:request) { double('request') }
before do
@@ -52,7 +53,10 @@ describe BuildEntity do
context 'when user is allowed to trigger action' do
before do
- build.project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
it 'contains path to play action' do
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index d2482ac434b..88ec4ed2952 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -19,7 +19,7 @@ describe PipelineEntity do
let(:pipeline) { create(:ci_empty_pipeline) }
it 'contains required fields' do
- expect(subject).to include :id, :user, :path, :coverage
+ expect(subject).to include :id, :user, :path, :coverage, :source
expect(subject).to include :ref, :commit
expect(subject).to include :updated_at, :created_at
end
@@ -36,7 +36,7 @@ describe PipelineEntity do
it 'contains flags' do
expect(subject).to include :flags
expect(subject[:flags])
- .to include :latest, :triggered, :stuck,
+ .to include :latest, :stuck,
:yaml_errors, :retryable, :cancelable
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index b536103ed65..8bf02f56282 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -9,13 +9,13 @@ describe Ci::CreatePipelineService, services: true do
end
describe '#execute' do
- def execute_service(after: project.commit.id, message: 'Message', ref: 'refs/heads/master')
+ def execute_service(source: :push, after: project.commit.id, message: 'Message', ref: 'refs/heads/master')
params = { ref: ref,
before: '00000000',
after: after,
commits: [{ message: message }] }
- described_class.new(project, user, params).execute
+ described_class.new(project, user, params).execute(source)
end
context 'valid params' do
@@ -30,13 +30,14 @@ describe Ci::CreatePipelineService, services: true do
it 'creates a pipeline' do
expect(pipeline).to be_kind_of(Ci::Pipeline)
expect(pipeline).to be_valid
+ expect(pipeline).to be_push
expect(pipeline).to eq(project.pipelines.last)
expect(pipeline).to have_attributes(user: user)
expect(pipeline).to have_attributes(status: 'pending')
expect(pipeline.builds.first).to be_kind_of(Ci::Build)
end
- context '#update_merge_requests_head_pipeline' do
+ context 'when merge requests already exist for this source branch' do
it 'updates head pipeline of each merge request' do
merge_request_1 = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
merge_request_2 = create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
@@ -58,7 +59,7 @@ describe Ci::CreatePipelineService, services: true do
end
context 'when merge request target project is different from source project' do
- let!(:target_project) { create(:empty_project) }
+ let!(:target_project) { create(:project) }
let!(:forked_project_link) { create(:forked_project_link, forked_to_project: project, forked_from_project: target_project) }
it 'updates head pipeline for merge request' do
@@ -70,6 +71,17 @@ describe Ci::CreatePipelineService, services: true do
expect(merge_request.reload.head_pipeline).to eq(head_pipeline)
end
end
+
+ context 'when merge request head commit sha does not match pipeline sha' do
+ it 'does not update merge request head pipeline' do
+ merge_request = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
+ allow_any_instance_of(MergeRequestDiff).to receive(:head_commit).and_return(double(id: 1234))
+
+ pipeline
+
+ expect(merge_request.reload.head_pipeline).to be_nil
+ end
+ end
end
context 'auto-cancel enabled' do
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index 5a20102872a..f2956262f4b 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -16,6 +16,7 @@ describe Ci::CreateTriggerRequestService, services: true do
context 'without owner' do
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
+ it { expect(subject.pipeline).to be_trigger }
it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
end
@@ -25,6 +26,7 @@ describe Ci::CreateTriggerRequestService, services: true do
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
+ it { expect(subject.pipeline).to be_trigger }
it { expect(subject.pipeline.user).to eq(owner) }
it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
it { expect(subject.builds.first.user).to eq(owner) }
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index d6f9fa42045..ea211de1f82 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -13,8 +13,11 @@ describe Ci::PlayBuildService, '#execute', :services do
context 'when project does not have repository yet' do
let(:project) { create(:empty_project) }
- it 'allows user with master role to play build' do
- project.add_master(user)
+ it 'allows user to play build if protected branch rules are met' do
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
service.execute(build)
@@ -45,7 +48,10 @@ describe Ci::PlayBuildService, '#execute', :services do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'enqueues the build' do
@@ -64,7 +70,10 @@ describe Ci::PlayBuildService, '#execute', :services do
let(:build) { create(:ci_build, when: :manual, pipeline: pipeline) }
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'duplicates the build' do
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index fc5de5d069a..1557cb3c938 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -333,10 +333,11 @@ describe Ci::ProcessPipelineService, '#execute', :services do
context 'when pipeline is promoted sequentially up to the end' do
before do
- # We are using create(:empty_project), and users has to be master in
- # order to execute manual action when repository does not exist.
+ # Users need ability to merge into a branch in order to trigger
+ # protected manual actions.
#
- project.add_master(user)
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
it 'properly processes entire pipeline' do
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index d941d56c0d8..3e860203063 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -6,9 +6,12 @@ describe Ci::RetryPipelineService, '#execute', :services do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:service) { described_class.new(project, user) }
- context 'when user has ability to modify pipeline' do
+ context 'when user has full ability to modify pipeline' do
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: pipeline.ref, project: project)
end
context 'when there are already retried jobs present' do
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index ab06f45dbb9..bcd1fb64ab9 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -131,6 +131,19 @@ describe GitPushService, services: true do
end
end
+ describe "Pipelines" do
+ subject { execute_service(project, user, @oldrev, @newrev, @ref) }
+
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ end
+
+ it "creates a new pipeline" do
+ expect{ subject }.to change{ Ci::Pipeline.count }
+ expect(Ci::Pipeline.last).to be_push
+ end
+ end
+
describe "Push Event" do
before do
service = execute_service(project, user, @oldrev, @newrev, @ref )
@@ -436,6 +449,7 @@ describe GitPushService, services: true do
author_name: commit_author.name,
author_email: commit_author.email
})
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
allow(project.repository).to receive_messages(commits_between: [closing_commit])
end
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index b73beb3f6fc..1fdcb420a8b 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -30,6 +30,20 @@ describe GitTagPushService, services: true do
end
end
+ describe "Pipelines" do
+ subject { service.execute }
+
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ project.team << [user, :developer]
+ end
+
+ it "creates a new pipeline" do
+ expect{ subject }.to change{ Ci::Pipeline.count }
+ expect(Ci::Pipeline.last).to be_push
+ end
+ end
+
describe "Git Tag Push Data" do
subject { @push_data }
let(:tag) { project.repository.find_tag(tag_name) }
diff --git a/spec/services/members/authorized_destroy_service_spec.rb b/spec/services/members/authorized_destroy_service_spec.rb
index 8a6732faa19..f99b11f208c 100644
--- a/spec/services/members/authorized_destroy_service_spec.rb
+++ b/spec/services/members/authorized_destroy_service_spec.rb
@@ -18,7 +18,7 @@ describe Members::AuthorizedDestroyService, services: true do
member = create :project_member, :invited, project: project
expect { described_class.new(member, member_user).execute }
- .to change { Member.count }.from(2).to(1)
+ .to change { Member.count }.from(3).to(2)
end
it 'destroys invited group member' do
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index b70e9d534a4..2963f62cc7d 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -75,6 +75,37 @@ describe MergeRequests::CreateService, services: true do
expect(Todo.where(attributes).count).to eq 1
end
end
+
+ context 'when head pipelines already exist for merge request source branch' do
+ let(:sha) { project.commit(opts[:source_branch]).id }
+ let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) }
+ let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) }
+ let!(:pipeline_3) { create(:ci_pipeline, project: project, ref: "other_branch", project_id: project.id) }
+
+ before do
+ project.merge_requests.
+ where(source_branch: opts[:source_branch], target_branch: opts[:target_branch]).
+ destroy_all
+ end
+
+ it 'sets head pipeline' do
+ merge_request = service.execute
+
+ expect(merge_request.head_pipeline).to eq(pipeline_2)
+ expect(merge_request).to be_persisted
+ end
+
+ context 'when merge request head commit sha does not match pipeline sha' do
+ it 'sets the head pipeline correctly' do
+ pipeline_2.update(sha: 1234)
+
+ merge_request = service.execute
+
+ expect(merge_request.head_pipeline).to eq(pipeline_1)
+ expect(merge_request).to be_persisted
+ end
+ end
+ end
end
it_behaves_like 'new issuable record that supports slash commands' do
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 4b8589b2736..0d6dd28e332 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -70,7 +70,7 @@ describe Projects::DestroyService, services: true do
end
end
- expect(project.team.members.count).to eq 1
+ expect(project.team.members.count).to eq 2
end
end
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 2112f1cf9ea..5cf989105d0 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -26,6 +26,15 @@ describe SearchService, services: true do
expect(project).to eq accessible_project
end
+
+ it 'returns the project for guests' do
+ search_project = create :empty_project
+ search_project.add_guest(user)
+
+ project = SearchService.new(user, project_id: search_project.id).project
+
+ expect(project).to eq search_project
+ end
end
context 'when the project is not accessible' do
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 5a7cfaff7fb..c499b1bb343 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -733,6 +733,26 @@ describe SystemNoteService, services: true do
jira_service_settings
end
+ def cross_reference(type, link_exists = false)
+ noteable = type == 'commit' ? commit : merge_request
+
+ links = []
+ if link_exists
+ url = if type == 'commit'
+ "#{Settings.gitlab.base_url}/#{project.namespace.path}/#{project.path}/commit/#{commit.id}"
+ else
+ "#{Settings.gitlab.base_url}/#{project.namespace.path}/#{project.path}/merge_requests/#{merge_request.iid}"
+ end
+ link = double(object: { 'url' => url })
+ links << link
+ expect(link).to receive(:save!)
+ end
+
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return(links)
+
+ described_class.cross_reference(jira_issue, noteable, author)
+ end
+
noteable_types = %w(merge_requests commit)
noteable_types.each do |type|
@@ -740,24 +760,39 @@ describe SystemNoteService, services: true do
it "blocks cross reference when #{type.underscore}_events is false" do
jira_tracker.update("#{type}_events" => false)
- noteable = type == "commit" ? commit : merge_request
- result = described_class.cross_reference(jira_issue, noteable, author)
-
- expect(result).to eq("Events for #{noteable.class.to_s.underscore.humanize.pluralize.downcase} are disabled.")
+ expect(cross_reference(type)).to eq("Events for #{type.pluralize.humanize.downcase} are disabled.")
end
it "blocks cross reference when #{type.underscore}_events is true" do
jira_tracker.update("#{type}_events" => true)
- noteable = type == "commit" ? commit : merge_request
- result = described_class.cross_reference(jira_issue, noteable, author)
+ expect(cross_reference(type)).to eq(success_message)
+ end
+ end
+
+ context 'when a new cross reference is created' do
+ it 'creates a new comment and remote link' do
+ cross_reference(type)
- expect(result).to eq(success_message)
+ expect(WebMock).to have_requested(:post, jira_api_comment_url(jira_issue))
+ expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue))
+ end
+ end
+
+ context 'when a link exists' do
+ it 'updates a link but does not create a new comment' do
+ expect(WebMock).not_to have_requested(:post, jira_api_comment_url(jira_issue))
+
+ cross_reference(type, true)
end
end
end
describe "new reference" do
+ before do
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
+ end
+
context 'for commits' do
it "creates comment" do
result = described_class.cross_reference(jira_issue, commit, author)
@@ -837,6 +872,7 @@ describe SystemNoteService, services: true do
describe "existing reference" do
before do
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\n'#{commit.title.chomp}'"
allow_any_instance_of(JIRA::Resource::Issue).to receive(:comments).and_return([OpenStruct.new(body: message)])
end
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index b19374ef1a2..8c40d25e00c 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -1,15 +1,13 @@
require 'spec_helper'
describe Users::RefreshAuthorizedProjectsService do
- let(:project) { create(:empty_project) }
+ # We're using let! here so that any expectations for the service class are not
+ # triggered twice.
+ let!(:project) { create(:empty_project) }
+
let(:user) { project.namespace.owner }
let(:service) { described_class.new(user) }
- def create_authorization(project, user, access_level = Gitlab::Access::MASTER)
- ProjectAuthorization.
- create!(project: project, user: user, access_level: access_level)
- end
-
describe '#execute', :redis do
it 'refreshes the authorizations using a lease' do
expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
@@ -31,7 +29,8 @@ describe Users::RefreshAuthorizedProjectsService do
it 'updates the authorized projects of the user' do
project2 = create(:empty_project)
- to_remove = create_authorization(project2, user)
+ to_remove = user.project_authorizations.
+ create!(project: project2, access_level: Gitlab::Access::MASTER)
expect(service).to receive(:update_authorizations).
with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
@@ -40,7 +39,10 @@ describe Users::RefreshAuthorizedProjectsService do
end
it 'sets the access level of a project to the highest available level' do
- to_remove = create_authorization(project, user, Gitlab::Access::DEVELOPER)
+ user.project_authorizations.delete_all
+
+ to_remove = user.project_authorizations.
+ create!(project: project, access_level: Gitlab::Access::DEVELOPER)
expect(service).to receive(:update_authorizations).
with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
@@ -61,34 +63,10 @@ describe Users::RefreshAuthorizedProjectsService do
service.update_authorizations([], [])
end
-
- context 'when the authorized projects column is not set' do
- before do
- user.update!(authorized_projects_populated: nil)
- end
-
- it 'populates the authorized projects column' do
- service.update_authorizations([], [])
-
- expect(user.authorized_projects_populated).to eq true
- end
- end
-
- context 'when the authorized projects column is set' do
- before do
- user.update!(authorized_projects_populated: true)
- end
-
- it 'does nothing' do
- expect(user).not_to receive(:set_authorized_projects_column)
-
- service.update_authorizations([], [])
- end
- end
end
it 'removes authorizations that should be removed' do
- authorization = create_authorization(project, user)
+ authorization = user.project_authorizations.find_by(project_id: project.id)
service.update_authorizations([authorization.project_id])
@@ -96,6 +74,8 @@ describe Users::RefreshAuthorizedProjectsService do
end
it 'inserts authorizations that should be added' do
+ user.project_authorizations.delete_all
+
service.update_authorizations([], [[user.id, project.id, Gitlab::Access::MASTER]])
authorizations = user.project_authorizations
@@ -105,16 +85,6 @@ describe Users::RefreshAuthorizedProjectsService do
expect(authorizations[0].project_id).to eq(project.id)
expect(authorizations[0].access_level).to eq(Gitlab::Access::MASTER)
end
-
- it 'populates the authorized projects column' do
- # make sure we start with a nil value no matter what the default in the
- # factory may be.
- user.update!(authorized_projects_populated: nil)
-
- service.update_authorizations([], [[user.id, project.id, Gitlab::Access::MASTER]])
-
- expect(user.authorized_projects_populated).to eq(true)
- end
end
describe '#fresh_access_levels_per_project' do
@@ -163,7 +133,7 @@ describe Users::RefreshAuthorizedProjectsService do
end
end
- context 'projects of subgroups of groups the user is a member of' do
+ context 'projects of subgroups of groups the user is a member of', :nested_groups do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let!(:other_project) { create(:empty_project, group: nested_group) }
@@ -191,7 +161,7 @@ describe Users::RefreshAuthorizedProjectsService do
end
end
- context 'projects shared with subgroups of groups the user is a member of' do
+ context 'projects shared with subgroups of groups the user is a member of', :nested_groups do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:other_project) { create(:empty_project) }
@@ -208,8 +178,6 @@ describe Users::RefreshAuthorizedProjectsService do
end
describe '#current_authorizations_per_project' do
- before { create_authorization(project, user) }
-
let(:hash) { service.current_authorizations_per_project }
it 'returns a Hash' do
@@ -233,13 +201,13 @@ describe Users::RefreshAuthorizedProjectsService do
describe '#current_authorizations' do
context 'without authorizations' do
it 'returns an empty list' do
+ user.project_authorizations.delete_all
+
expect(service.current_authorizations.empty?).to eq(true)
end
end
context 'with an authorization' do
- before { create_authorization(project, user) }
-
let(:row) { service.current_authorizations.take }
it 'returns the currently authorized projects' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 51571ddebe9..4c2eba8fa46 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -92,6 +92,14 @@ RSpec.configure do |config|
Gitlab::Redis.with(&:flushall)
Sidekiq.redis(&:flushall)
end
+
+ config.around(:each, :nested_groups) do |example|
+ example.run if Group.supports_nested_groups?
+ end
+
+ config.around(:each, :postgresql) do |example|
+ example.run if Gitlab::Database.postgresql?
+ end
end
FactoryGirl::SyntaxRunner.class_eval do
diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb
index 7aca902fc61..2bf159002a0 100644
--- a/spec/support/gitaly.rb
+++ b/spec/support/gitaly.rb
@@ -1,6 +1,7 @@
if Gitlab::GitalyClient.enabled?
RSpec.configure do |config|
- config.before(:each) do
+ config.before(:each) do |example|
+ next if example.metadata[:skip_gitaly_mock]
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 3c000feba5d..72b3b226c1e 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -40,7 +40,7 @@ module TestEnv
'wip' => 'b9238ee',
'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3',
- 'add-ipython-files' => '6d85bb6',
+ 'add-ipython-files' => '93ee732',
'add-pdf-file' => 'e774ebd'
}.freeze
diff --git a/spec/validators/dynamic_path_validator_spec.rb b/spec/validators/dynamic_path_validator_spec.rb
index 5f998e78f07..8dbf3eecd23 100644
--- a/spec/validators/dynamic_path_validator_spec.rb
+++ b/spec/validators/dynamic_path_validator_spec.rb
@@ -3,6 +3,28 @@ require 'spec_helper'
describe DynamicPathValidator do
let(:validator) { described_class.new(attributes: [:path]) }
+ def expect_handles_invalid_utf8
+ expect { yield('\255invalid') }.to be_falsey
+ end
+
+ describe '.valid_user_path' do
+ it 'handles invalid utf8' do
+ expect(described_class.valid_user_path?("a\0weird\255path")).to be_falsey
+ end
+ end
+
+ describe '.valid_group_path' do
+ it 'handles invalid utf8' do
+ expect(described_class.valid_group_path?("a\0weird\255path")).to be_falsey
+ end
+ end
+
+ describe '.valid_project_path' do
+ it 'handles invalid utf8' do
+ expect(described_class.valid_project_path?("a\0weird\255path")).to be_falsey
+ end
+ end
+
describe '#path_valid_for_record?' do
context 'for project' do
it 'calls valid_project_path?' do
diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb
index c62450fb8e2..72323da2838 100644
--- a/spec/views/ci/status/_badge.html.haml_spec.rb
+++ b/spec/views/ci/status/_badge.html.haml_spec.rb
@@ -16,7 +16,7 @@ describe 'ci/status/_badge', :view do
end
it 'has link to build details page' do
- details_path = namespace_project_build_path(
+ details_path = namespace_project_job_path(
project.namespace, project, build)
render_status(build)
diff --git a/spec/views/projects/blob/_viewer.html.haml_spec.rb b/spec/views/projects/blob/_viewer.html.haml_spec.rb
index c6b0ed8da3c..bbd7f98fa8d 100644
--- a/spec/views/projects/blob/_viewer.html.haml_spec.rb
+++ b/spec/views/projects/blob/_viewer.html.haml_spec.rb
@@ -10,8 +10,8 @@ describe 'projects/blob/_viewer.html.haml', :view do
include BlobViewer::Rich
self.partial_name = 'text'
- self.overridable_max_size = 1.megabyte
- self.max_size = 5.megabytes
+ self.collapse_limit = 1.megabyte
+ self.size_limit = 5.megabytes
self.load_async = true
end
end
diff --git a/spec/views/projects/builds/_build.html.haml_spec.rb b/spec/views/projects/jobs/_build.html.haml_spec.rb
index 751482cac42..1d58891036e 100644
--- a/spec/views/projects/builds/_build.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_build.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/ci/builds/_build' do
+describe 'projects/ci/jobs/_build' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/builds/_generic_commit_status.html.haml_spec.rb b/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
index dc2ffc9dc47..dc2ffc9dc47 100644
--- a/spec/views/projects/builds/_generic_commit_status.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 0f39df0f250..8f2822f5dc5 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/builds/show', :view do
+describe 'projects/jobs/show', :view do
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
@@ -278,7 +278,7 @@ describe 'projects/builds/show', :view do
it 'links to issues/new with the title and description filled in' do
title = "Build Failed ##{build.id}"
- build_url = namespace_project_build_url(project.namespace, project, build)
+ build_url = namespace_project_job_url(project.namespace, project, build)
href = new_namespace_project_issue_path(
project.namespace,
project,
diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb
index 9c650354d72..14ed8b7811e 100644
--- a/spec/workers/pipeline_schedule_worker_spec.rb
+++ b/spec/workers/pipeline_schedule_worker_spec.rb
@@ -23,7 +23,8 @@ describe PipelineScheduleWorker do
context 'when there is a scheduled pipeline within next_run_at' do
it 'creates a new pipeline' do
- expect { subject }.to change { project.pipelines.count }.by(1)
+ expect{ subject }.to change { project.pipelines.count }.by(1)
+ expect(Ci::Pipeline.last).to be_schedule
end
it 'updates the next_run_at field' do