summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorLuke "Jared" Bennett <lbennett@gitlab.com>2017-06-02 17:17:54 +0100
committerLuke "Jared" Bennett <lbennett@gitlab.com>2017-06-02 17:17:54 +0100
commit5a4a08249055022e9e6e30d9ab38c45c1bf71842 (patch)
treeeda99155dee262f3965a7938c9184e73022cf48a /spec
parente591401b0b3f08baf4cd28d0fcb8e184a515fc74 (diff)
parentaea03d7cdfb882762426dbb5cab805682afe5e2a (diff)
downloadgitlab-ce-5a4a08249055022e9e6e30d9ab38c45c1bf71842.tar.gz
Merge remote-tracking branch 'origin/master' into fix-realtime-edited-text-for-issues-9-3
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb48
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb9
-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/services_controller_spec.rb112
-rw-r--r--spec/db/production/settings.rb1
-rw-r--r--spec/factories/ci/pipelines.rb1
-rw-r--r--spec/factories/ci/variables.rb4
-rw-r--r--spec/factories/services.rb6
-rw-r--r--spec/features/admin/admin_builds_spec.rb16
-rw-r--r--spec/features/admin/admin_users_spec.rb2
-rw-r--r--spec/features/commits_spec.rb20
-rw-r--r--spec/features/container_registry_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb19
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb19
-rw-r--r--spec/features/issues/move_spec.rb8
-rw-r--r--spec/features/merge_requests/discussion_spec.rb41
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb2
-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/blobs/blob_line_permalink_updater_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_spec.rb57
-rw-r--r--spec/features/projects/files/browse_files_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb (renamed from spec/features/projects/builds_spec.rb)145
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb2
-rw-r--r--spec/features/projects/services/jira_service_spec.rb92
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb18
-rw-r--r--spec/features/projects/services/slack_slash_command_spec.rb14
-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/features/variables_spec.rb48
-rw-r--r--spec/finders/projects_finder_spec.rb15
-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/avatars_helper_spec.rb101
-rw-r--r--spec/helpers/blob_helper_spec.rb28
-rw-r--r--spec/helpers/diff_helper_spec.rb37
-rw-r--r--spec/helpers/issuables_helper_spec.rb29
-rw-r--r--spec/javascripts/build_spec.js2
-rw-r--r--spec/javascripts/droplab/plugins/ajax_filter_spec.js72
-rw-r--r--spec/javascripts/environments/environments_store_spec.js9
-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/fixtures/services.rb31
-rw-r--r--spec/javascripts/integrations/integration_settings_form_spec.js199
-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/merge_request_spec.js4
-rw-r--r--spec/javascripts/notebook/cells/markdown_spec.js57
-rw-r--r--spec/javascripts/notes_spec.js4
-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/job_component_spec.js24
-rw-r--r--spec/javascripts/pipelines/header_component_spec.js60
-rw-r--r--spec/javascripts/pipelines/pipeline_details_mediator_spec.js41
-rw-r--r--spec/javascripts/pipelines/pipeline_store_spec.js27
-rw-r--r--spec/javascripts/pipelines/pipeline_url_spec.js1
-rw-r--r--spec/javascripts/vue_shared/components/commit_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js11
-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/javascripts/vue_shared/components/pipelines_table_row_spec.js4
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb5
-rw-r--r--spec/lib/feature_spec.rb26
-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/encoding_helper_spec.rb (renamed from spec/lib/gitlab/git/encoding_helper_spec.rb)4
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb11
-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/repository_spec.rb15
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb82
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/utils_spec.rb11
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb223
-rw-r--r--spec/lib/system_check_spec.rb36
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb117
-rw-r--r--spec/models/blob_viewer/base_spec.rb70
-rw-r--r--spec/models/ci/build_spec.rb68
-rw-r--r--spec/models/ci/pipeline_spec.rb26
-rw-r--r--spec/models/ci/variable_spec.rb33
-rw-r--r--spec/models/deployment_spec.rb13
-rw-r--r--spec/models/diff_note_spec.rb27
-rw-r--r--spec/models/environment_spec.rb27
-rw-r--r--spec/models/merge_request_spec.rb14
-rw-r--r--spec/models/project_services/jira_service_spec.rb35
-rw-r--r--spec/models/project_spec.rb98
-rw-r--r--spec/models/project_team_spec.rb151
-rw-r--r--spec/models/user_spec.rb21
-rw-r--r--spec/requests/api/commit_statuses_spec.rb4
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/api/features_spec.rb104
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb297
-rw-r--r--spec/requests/api/projects_spec.rb34
-rw-r--r--spec/requests/api/v3/commits_spec.rb4
-rw-r--r--spec/requests/api/v3/deploy_keys_spec.rb9
-rw-r--r--spec/requests/api/variables_spec.rb7
-rw-r--r--spec/rubocop/cop/activerecord_serialize_spec.rb33
-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/serializers/user_entity_spec.rb6
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb10
-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/discussions/update_diff_position_service_spec.rb (renamed from spec/services/notes/diff_position_update_service_spec.rb)28
-rw-r--r--spec/services/git_push_service_spec.rb13
-rw-r--r--spec/services/git_tag_push_service_spec.rb14
-rw-r--r--spec/services/gravatar_service_spec.rb20
-rw-r--r--spec/services/merge_requests/conflicts/resolve_service_spec.rb42
-rw-r--r--spec/services/users/destroy_service_spec.rb8
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb40
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb19
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb42
-rw-r--r--spec/spec_helper.rb3
-rw-r--r--spec/support/gitaly.rb3
-rw-r--r--spec/support/matchers/execute_check.rb23
-rw-r--r--spec/support/rake_helpers.rb5
-rw-r--r--spec/support/test_env.rb2
-rw-r--r--spec/uploaders/artifact_uploader_spec.rb38
-rw-r--r--spec/uploaders/gitlab_uploader_spec.rb56
-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
145 files changed, 4080 insertions, 643 deletions
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..f6840578145 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -57,6 +57,11 @@ describe Projects::EnvironmentsController do
expect(json_response['available_count']).to eq 3
expect(json_response['stopped_count']).to eq 1
end
+
+ it 'sets the polling interval header' do
+ expect(response).to have_http_status(:ok)
+ expect(response.headers['Poll-Interval']).to eq("3000")
+ end
end
context 'when requesting stopped environments scope' do
@@ -177,7 +182,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 +196,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/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 2d892f4a2b7..23b463c0082 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -3,7 +3,9 @@ require 'spec_helper'
describe Projects::ServicesController do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
- let(:service) { create(:service, project: project) }
+ let(:service) { create(:hipchat_service, project: project) }
+ let(:hipchat_client) { { '#room' => double(send: true) } }
+ let(:service_params) { { token: 'hipchat_token_p', room: '#room' } }
before do
sign_in(user)
@@ -13,97 +15,81 @@ describe Projects::ServicesController do
controller.instance_variable_set(:@service, service)
end
- shared_examples_for 'services controller' do |referrer|
- before do
- request.env["HTTP_REFERER"] = referrer
- end
-
- describe "#test" do
- context 'when can_test? returns false' do
- it 'renders 404' do
- allow_any_instance_of(Service).to receive(:can_test?).and_return(false)
+ describe '#test' do
+ context 'when can_test? returns false' do
+ it 'renders 404' do
+ allow_any_instance_of(Service).to receive(:can_test?).and_return(false)
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id
- expect(response).to have_http_status(404)
- end
+ expect(response).to have_http_status(404)
end
+ end
- context 'success' do
- context 'with empty project' do
- let(:project) { create(:empty_project) }
-
- context 'with chat notification service' do
- let(:service) { project.create_microsoft_teams_service(webhook: 'http://webhook.com') }
-
- it 'redirects and show success message' do
- allow_any_instance_of(MicrosoftTeams::Notifier).to receive(:ping).and_return(true)
-
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ context 'success' do
+ context 'with empty project' do
+ let(:project) { create(:empty_project) }
- expect(response).to redirect_to(root_path)
- expect(flash[:notice]).to eq('We sent a request to the provided URL')
- end
- end
+ context 'with chat notification service' do
+ let(:service) { project.create_microsoft_teams_service(webhook: 'http://webhook.com') }
- it 'redirects and show success message' do
- expect(service).to receive(:test).and_return(success: true, result: 'done')
+ it 'returns success' do
+ allow_any_instance_of(MicrosoftTeams::Notifier).to receive(:ping).and_return(true)
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id
- expect(response).to redirect_to(root_path)
- expect(flash[:notice]).to eq('We sent a request to the provided URL')
+ expect(response.status).to eq(200)
end
end
- it "redirects and show success message" do
- expect(service).to receive(:test).and_return(success: true, result: 'done')
+ it 'returns success' do
+ expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client)
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: service_params
- expect(response).to redirect_to(root_path)
- expect(flash[:notice]).to eq('We sent a request to the provided URL')
+ expect(response.status).to eq(200)
end
end
- context 'failure' do
- it "redirects and show failure message" do
- expect(service).to receive(:test).and_return(success: false, result: 'Bad test')
+ it 'returns success' do
+ expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client)
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: service_params
- expect(response).to redirect_to(root_path)
- expect(flash[:alert]).to eq('We tried to send a request to the provided URL but an error occurred: Bad test')
- end
+ expect(response.status).to eq(200)
end
end
- end
- describe 'referrer defined' do
- it_should_behave_like 'services controller' do
- let!(:referrer) { "/" }
- end
- end
+ context 'failure' do
+ it 'returns success status code and the error message' do
+ expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_raise('Bad test')
- describe 'referrer undefined' do
- it_should_behave_like 'services controller' do
- let!(:referrer) { nil }
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: service_params
+
+ expect(response.status).to eq(200)
+ expect(JSON.parse(response.body)).
+ to eq('error' => true, 'message' => 'Test failed.', 'service_response' => 'Bad test')
+ end
end
end
describe 'PUT #update' do
- context 'on successful update' do
- it 'sets the flash' do
- expect(service).to receive(:to_param).and_return('hipchat')
- expect(service).to receive(:event_names).and_return(HipchatService.event_names)
+ context 'when param `active` is set to true' do
+ it 'activates the service and redirects to integrations paths' do
+ put :update,
+ namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: { active: true }
+
+ expect(response).to redirect_to(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(flash[:notice]).to eq 'HipChat activated.'
+ end
+ end
+ context 'when param `active` is set to false' do
+ it 'does not activate the service but saves the settings' do
put :update,
- namespace_id: project.namespace.id,
- project_id: project.id,
- id: service.id,
- service: { active: false }
+ namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: { active: false }
- expect(flash[:notice]).to eq 'Successfully updated.'
+ expect(flash[:notice]).to eq 'HipChat settings saved, but not activated.'
end
end
end
diff --git a/spec/db/production/settings.rb b/spec/db/production/settings.rb
index 007b35bbb77..3cbb173c4cc 100644
--- a/spec/db/production/settings.rb
+++ b/spec/db/production/settings.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-require 'rainbow/ext/string'
describe 'seed production settings', lib: true do
include StubENV
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/ci/variables.rb b/spec/factories/ci/variables.rb
index c5fba597c1c..f83366136fd 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -3,6 +3,10 @@ FactoryGirl.define do
sequence(:key) { |n| "VARIABLE_#{n}" }
value 'VARIABLE_VALUE'
+ trait(:protected) do
+ protected true
+ end
+
project factory: :empty_project
end
end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 3fad4d2d658..e7366a7fd1c 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -33,4 +33,10 @@ FactoryGirl.define do
project_key: 'jira-key'
)
end
+
+ factory :hipchat_service do
+ project factory: :empty_project
+ type 'HipchatService'
+ token 'test_token'
+ 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_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 12cf59f42b0..376e80571d0 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -21,6 +21,8 @@ describe "Admin::Users", feature: true do
expect(page).to have_content(current_user.name)
expect(page).to have_content(user.email)
expect(page).to have_content(user.name)
+ expect(page).to have_link('Block', href: block_admin_user_path(user))
+ expect(page).to have_link('Delete', href: admin_user_path(user))
end
describe 'Two-factor Authentication filters' do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index e6c4ab24de5..2772f05982a 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -76,7 +76,7 @@ describe 'Commits' do
end
end
- describe 'Commit builds' do
+ describe 'Commit builds', :feature, :js do
before do
visit ci_status_path(pipeline)
end
@@ -85,7 +85,6 @@ describe 'Commits' do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
- expect(page).to have_content pipeline.created_at.strftime('%b %d, %Y')
end
end
@@ -102,7 +101,7 @@ describe 'Commits' do
end
describe 'Cancel all builds' do
- it 'cancels commit' do
+ it 'cancels commit', :js do
visit ci_status_path(pipeline)
click_on 'Cancel running'
expect(page).to have_content 'canceled'
@@ -110,9 +109,9 @@ describe 'Commits' do
end
describe 'Cancel build' do
- it 'cancels build' do
+ it 'cancels build', :js do
visit ci_status_path(pipeline)
- find('a.btn[title="Cancel"]').click
+ find('.js-btn-cancel-pipeline').click
expect(page).to have_content 'canceled'
end
end
@@ -152,17 +151,20 @@ describe 'Commits' do
visit ci_status_path(pipeline)
end
- it do
+ it 'Renders header', :feature, :js do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
- expect(page).to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry')
end
+
+ it do
+ expect(page).to have_link('Download artifacts')
+ end
end
- context 'when accessing internal project with disallowed access' do
+ context 'when accessing internal project with disallowed access', :feature, :js do
before do
project.update(
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
@@ -175,7 +177,7 @@ describe 'Commits' do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
- expect(page).not_to have_link('Download artifacts')
+
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry')
end
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index b86609e07c5..fa7adbe71ea 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -19,7 +19,7 @@ describe "Container Registry" do
scenario 'user visits container registry main page' do
visit_container_registry
- expect(page).to have_content 'No container image repositories'
+ expect(page).to have_content 'No container images'
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 4d38df05928..44353d880c2 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -157,6 +157,25 @@ describe 'Dropdown assignee', :feature, :js do
end
end
+ describe 'selecting from dropdown without Ajax call' do
+ before do
+ Gitlab::Testing::RequestBlockerMiddleware.block_requests!
+ filtered_search.set('assignee:')
+ end
+
+ after do
+ Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
+ end
+
+ it 'selects current user' do
+ find('#js-dropdown-assignee .filter-dropdown-item', text: user.username).click
+
+ expect(page).to have_css(js_dropdown_assignee, visible: false)
+ expect_tokens([{ name: 'assignee', value: user.username }])
+ expect_filtered_search_input_empty
+ end
+ end
+
describe 'input has existing content' do
it 'opens assignee dropdown with existing search term' do
filtered_search.set('searchTerm assignee:')
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 358b244fb5b..6b707c4be4a 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -135,6 +135,25 @@ describe 'Dropdown author', js: true, feature: true do
end
end
+ describe 'selecting from dropdown without Ajax call' do
+ before do
+ Gitlab::Testing::RequestBlockerMiddleware.block_requests!
+ filtered_search.set('author:')
+ end
+
+ after do
+ Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
+ end
+
+ it 'selects current user' do
+ find('#js-dropdown-author .filter-dropdown-item', text: user.username).click
+
+ expect(page).to have_css(js_dropdown_author, visible: false)
+ expect_tokens([{ name: 'author', value: user.username }])
+ expect_filtered_search_input_empty
+ end
+ end
+
describe 'input has existing content' do
it 'opens author dropdown with existing search term' do
filtered_search.set('searchTerm author:')
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/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb
index 1a09cc54c2e..9db235f35ba 100644
--- a/spec/features/merge_requests/discussion_spec.rb
+++ b/spec/features/merge_requests/discussion_spec.rb
@@ -5,7 +5,7 @@ feature 'Merge Request Discussions', feature: true do
login_as :admin
end
- context "Diff discussions" do
+ describe "Diff discussions" do
let(:merge_request) { create(:merge_request, importing: true) }
let(:project) { merge_request.source_project }
let!(:old_merge_request_diff) { merge_request.merge_request_diffs.create(diff_refs: outdated_diff_refs) }
@@ -48,4 +48,43 @@ feature 'Merge Request Discussions', feature: true do
end
end
end
+
+ describe 'Commit comments displayed in MR context', :js do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+
+ shared_examples 'a functional discussion' do
+ let(:discussion_id) { note.discussion_id(merge_request) }
+
+ it 'is displayed' do
+ expect(page).to have_css(".discussion[data-discussion-id='#{discussion_id}']")
+ end
+
+ it 'can be replied to' do
+ within(".discussion[data-discussion-id='#{discussion_id}']") do
+ click_button 'Reply...'
+ fill_in 'note[note]', with: 'Test!'
+ click_button 'Comment'
+
+ expect(page).to have_css('.note', count: 2)
+ end
+ end
+ end
+
+ before(:each) do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ context 'a regular commit comment' do
+ let(:note) { create(:note_on_commit, project: project) }
+
+ it_behaves_like 'a functional discussion'
+ end
+
+ context 'a commit diff comment' do
+ let(:note) { create(:diff_note_on_commit, project: project) }
+
+ it_behaves_like 'a functional discussion'
+ end
+ 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/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/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
index d94204230f6..53c5a52ce3a 100644
--- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
+++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
@@ -55,7 +55,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
end
end
- describe 'Click "Blame" button' do
+ describe 'Click "Annotate" button' do
it 'works with no initial line number fragment hash' do
visit_blob
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/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
index c0a9327249c..30a1eedbb48 100644
--- a/spec/features/projects/files/browse_files_spec.rb
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -12,7 +12,7 @@ feature 'user browses project', feature: true, js: true do
scenario "can see blame of '.gitignore'" do
click_link ".gitignore"
- click_link 'Blame'
+ click_link 'Annotate'
expect(page).to have_content "*.rb"
expect(page).to have_content "Dmitriy Zaporozhets"
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/jobs_spec.rb
index 8f4dfa7c48b..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,10 +197,25 @@ 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
@@ -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,30 +381,30 @@ 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', :js 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.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
find('.js-raw-link-controller').click()
end
@@ -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.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
@@ -410,7 +439,7 @@ feature 'Builds', :feature do
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', :js do
@@ -429,7 +458,7 @@ 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
@@ -437,38 +466,52 @@ feature 'Builds', :feature do
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/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index cfac54ef259..36a3ddca6ef 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -229,7 +229,6 @@ describe 'Pipeline', :feature, :js do
before { find('.js-retry-button').trigger('click') }
it { expect(page).not_to have_content('Retry') }
- it { expect(page).to have_selector('.retried') }
end
end
@@ -240,7 +239,6 @@ describe 'Pipeline', :feature, :js do
before { click_on 'Cancel running' }
it { expect(page).not_to have_content('Cancel running') }
- it { expect(page).to have_selector('.ci-canceled') }
end
end
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/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb
new file mode 100644
index 00000000000..c96d87e5708
--- /dev/null
+++ b/spec/features/projects/services/jira_service_spec.rb
@@ -0,0 +1,92 @@
+require 'spec_helper'
+
+feature 'Setup Jira service', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:service) { project.create_jira_service }
+
+ let(:url) { 'http://jira.example.com' }
+ let(:project_url) { 'http://username:password@jira.example.com/rest/api/2/project/GitLabProject' }
+
+ def fill_form(active = true)
+ check 'Active' if active
+
+ fill_in 'service_url', with: url
+ fill_in 'service_project_key', with: 'GitLabProject'
+ fill_in 'service_username', with: 'username'
+ fill_in 'service_password', with: 'password'
+ fill_in 'service_jira_issue_transition_id', with: '25'
+ end
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_settings_integrations_path(project.namespace, project)
+ end
+
+ describe 'user sets and activates Jira Service' do
+ context 'when Jira connection test succeeds' do
+ before do
+ WebMock.stub_request(:get, project_url)
+ end
+
+ it 'activates the JIRA service' do
+ click_link('JIRA')
+ fill_form
+ click_button('Test settings and save changes')
+ wait_for_requests
+
+ expect(page).to have_content('JIRA activated.')
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ end
+ end
+
+ context 'when Jira connection test fails' do
+ before do
+ WebMock.stub_request(:get, project_url).to_return(status: 401)
+ end
+
+ it 'shows errors when some required fields are not filled in' do
+ click_link('JIRA')
+
+ check 'Active'
+ fill_in 'service_password', with: 'password'
+ click_button('Test settings and save changes')
+
+ page.within('.service-settings') do
+ expect(page).to have_content('This field is required.')
+ end
+ end
+
+ it 'activates the JIRA service' do
+ click_link('JIRA')
+ fill_form
+ click_button('Test settings and save changes')
+ wait_for_requests
+
+ expect(find('.flash-container-page')).to have_content 'Test failed.'
+ expect(find('.flash-container-page')).to have_content 'Save anyway'
+
+ find('.flash-alert .flash-action').trigger('click')
+ wait_for_requests
+
+ expect(page).to have_content('JIRA activated.')
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ end
+ end
+ end
+
+ describe 'user sets Jira Service but keeps it disabled' do
+ context 'when Jira connection test succeeds' do
+ it 'activates the JIRA service' do
+ click_link('JIRA')
+ fill_form(false)
+ click_button('Save changes')
+
+ expect(page).to have_content('JIRA settings saved, but not activated.')
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index dc3854262e7..1fe82222e59 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -24,15 +24,25 @@ feature 'Setup Mattermost slash commands', :feature, :js do
expect(token_placeholder).to eq('XXxxXXxxXXxxXXxxXXxxXXxx')
end
- it 'shows the token after saving' do
+ it 'redirects to the integrations page after saving but not activating' do
token = ('a'..'z').to_a.join
fill_in 'service_token', with: token
- click_on 'Save'
+ click_on 'Save changes'
- value = find_field('service_token').value
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(page).to have_content('Mattermost slash commands settings saved, but not activated.')
+ end
+
+ it 'redirects to the integrations page after activating' do
+ token = ('a'..'z').to_a.join
+
+ fill_in 'service_token', with: token
+ check 'service_active'
+ click_on 'Save changes'
- expect(value).to eq(token)
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(page).to have_content('Mattermost slash commands activated.')
end
it 'shows the add to mattermost button' do
diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb
index db903a0c8f0..f53b820c460 100644
--- a/spec/features/projects/services/slack_slash_command_spec.rb
+++ b/spec/features/projects/services/slack_slash_command_spec.rb
@@ -21,13 +21,21 @@ feature 'Slack slash commands', feature: true do
expect(page).to have_content('This service allows users to perform common')
end
- it 'shows the token after saving' do
+ it 'redirects to the integrations page after saving but not activating' do
fill_in 'service_token', with: 'token'
click_on 'Save'
- value = find_field('service_token').value
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(page).to have_content('Slack slash commands settings saved, but not activated.')
+ end
+
+ it 'redirects to the integrations page after activating' do
+ fill_in 'service_token', with: 'token'
+ check 'service_active'
+ click_on 'Save'
- expect(value).to eq('token')
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(page).to have_content('Slack slash commands activated.')
end
it 'shows the correct trigger url' do
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/features/variables_spec.rb b/spec/features/variables_spec.rb
index b83a230c1f8..d0c982919db 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -19,7 +19,7 @@ describe 'Project variables', js: true do
end
end
- it 'adds new variable' do
+ it 'adds new secret variable' do
fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value')
click_button('Add new variable')
@@ -27,6 +27,7 @@ describe 'Project variables', js: true do
expect(page).to have_content('Variables were successfully updated.')
page.within('.variables-table') do
expect(page).to have_content('key')
+ expect(page).to have_content('No')
end
end
@@ -41,6 +42,19 @@ describe 'Project variables', js: true do
end
end
+ it 'adds new protected variable' do
+ fill_in('variable_key', with: 'key')
+ fill_in('variable_value', with: 'value')
+ check('Protected')
+ click_button('Add new variable')
+
+ expect(page).to have_content('Variables were successfully updated.')
+ page.within('.variables-table') do
+ expect(page).to have_content('key')
+ expect(page).to have_content('Yes')
+ end
+ end
+
it 'reveals and hides new variable' do
fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value')
@@ -85,7 +99,7 @@ describe 'Project variables', js: true do
click_button('Save variable')
expect(page).to have_content('Variable was successfully updated.')
- expect(project.variables.first.value).to eq('key value')
+ expect(project.variables(true).first.value).to eq('key value')
end
it 'edits variable with empty value' do
@@ -98,6 +112,34 @@ describe 'Project variables', js: true do
click_button('Save variable')
expect(page).to have_content('Variable was successfully updated.')
- expect(project.variables.first.value).to eq('')
+ expect(project.variables(true).first.value).to eq('')
+ end
+
+ it 'edits variable to be protected' do
+ page.within('.variables-table') do
+ find('.btn-variable-edit').click
+ end
+
+ expect(page).to have_content('Update variable')
+ check('Protected')
+ click_button('Save variable')
+
+ expect(page).to have_content('Variable was successfully updated.')
+ expect(project.variables(true).first).to be_protected
+ end
+
+ it 'edits variable to be unprotected' do
+ project.variables.first.update(protected: true)
+
+ page.within('.variables-table') do
+ find('.btn-variable-edit').click
+ end
+
+ expect(page).to have_content('Update variable')
+ uncheck('Protected')
+ click_button('Save variable')
+
+ expect(page).to have_content('Variable was successfully updated.')
+ expect(project.variables(true).first).not_to be_protected
end
end
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/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/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 6157abfe339..049475a5408 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
describe AvatarsHelper do
+ include ApplicationHelper
+
let(:user) { create(:user) }
describe '#user_avatar' do
@@ -18,4 +20,103 @@ describe AvatarsHelper do
is_expected.to include(CGI.escapeHTML(user.avatar_url(size: 16)))
end
end
+
+ describe '#user_avatar_without_link' do
+ let(:options) { { user: user } }
+ subject { helper.user_avatar_without_link(options) }
+
+ it 'displays user avatar' do
+ is_expected.to eq image_tag(
+ avatar_icon(user, 16),
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+
+ context 'with css_class parameter' do
+ let(:options) { { user: user, css_class: '.cat-pics' } }
+
+ it 'uses provided css_class' do
+ is_expected.to eq image_tag(
+ avatar_icon(user, 16),
+ class: "avatar has-tooltip s16 #{options[:css_class]}",
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+ end
+
+ context 'with lazy parameter' do
+ let(:options) { { user: user, lazy: true } }
+
+ it 'uses data-src instead of src' do
+ is_expected.to eq image_tag(
+ '',
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body', src: avatar_icon(user, 16) }
+ )
+ end
+ end
+
+ context 'with size parameter' do
+ let(:options) { { user: user, size: 99 } }
+
+ it 'uses provided size' do
+ is_expected.to eq image_tag(
+ avatar_icon(user, options[:size]),
+ class: "avatar has-tooltip s#{options[:size]} ",
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+ end
+
+ context 'with url parameter' do
+ let(:options) { { user: user, url: '/over/the/rainbow.png' } }
+
+ it 'uses provided url' do
+ is_expected.to eq image_tag(
+ options[:url],
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+ end
+
+ context 'with user_name parameter' do
+ let(:options) { { user_name: 'Tinky Winky', user_email: 'no@f.un' } }
+
+ context 'with user parameter' do
+ let(:options) { { user: user, user_name: 'Tinky Winky' } }
+
+ it 'prefers user parameter' do
+ is_expected.to eq image_tag(
+ avatar_icon(user, 16),
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+ end
+
+ it 'uses user_name and user_email parameter if user is not present' do
+ is_expected.to eq image_tag(
+ avatar_icon(options[:user_email], 16),
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{options[:user_name]}'s avatar",
+ title: options[:user_name],
+ data: { container: 'body' }
+ )
+ end
+ end
+ end
end
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..a74615e07f9 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
@@ -129,6 +129,33 @@ describe DiffHelper do
end
end
+ describe '#parallel_diff_discussions' do
+ let(:discussion) { { 'abc_3_3' => 'comment' } }
+ let(:diff_file) { double(line_code: 'abc_3_3') }
+
+ before do
+ helper.instance_variable_set(:@grouped_diff_discussions, discussion)
+ end
+
+ it 'does not put comments on nonewline lines' do
+ left = Gitlab::Diff::Line.new('\\nonewline', 'old-nonewline', 3, 3, 3)
+ right = Gitlab::Diff::Line.new('\\nonewline', 'new-nonewline', 3, 3, 3)
+
+ result = helper.parallel_diff_discussions(left, right, diff_file)
+
+ expect(result).to eq([nil, nil])
+ end
+
+ it 'puts comments on added lines' do
+ left = Gitlab::Diff::Line.new('\\nonewline', 'old-nonewline', 3, 3, 3)
+ right = Gitlab::Diff::Line.new('new line', 'add', 3, 3, 3)
+
+ result = helper.parallel_diff_discussions(left, right, diff_file)
+
+ expect(result).to eq([nil, 'comment'])
+ end
+ end
+
describe "#diff_match_line" do
let(:old_pos) { 40 }
let(:new_pos) { 50 }
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 728fdfc85f2..8fcf7f5fa15 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -193,39 +193,14 @@ describe IssuablesHelper do
end
end
- describe '#issuable_app_data' do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:issue) { create(:issue, project: project, last_edited_by: user, created_at: 3.days.ago, updated_at: 2.days.ago, last_edited_at: 2.days.ago) }
- let(:issue_app_data) do
- {
- endpoint: realtime_changes_namespace_project_issue_path(project.namespace, project, issue),
- 'can-update' => "true",
- 'issuable-ref' => issue.to_reference || '',
- updated_at: issue.updated_at.to_time.iso8601,
- updated_by: {
- name: user.name,
- path: user_path(user)
- }
- }
- end
-
- before do
- allow(helper).to receive(:current_user).and_return(nil)
- allow(helper).to receive(:can?).with(nil, :update_issue, issue).and_return(true)
- end
-
- it { expect(helper.issuable_app_data(issue.project, issue)).to eq(issue_app_data) }
- end
-
describe '#updated_at_by' do
let(:user) { create(:user) }
let(:unedited_issuable) { create(:issue) }
let(:edited_issuable) { create(:issue, last_edited_by: user, created_at: 3.days.ago, updated_at: 2.days.ago, last_edited_at: 2.days.ago) }
let(:edited_updated_at_by) do
{
- updated_at: edited_issuable.updated_at.to_time.iso8601,
- updated_by: {
+ updatedAt: edited_issuable.updated_at.to_time.iso8601,
+ updatedBy: {
name: user.name,
path: user_path(user)
}
diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js
index 278bd1f9179..461908f3fde 100644
--- a/spec/javascripts/build_spec.js
+++ b/spec/javascripts/build_spec.js
@@ -8,7 +8,7 @@ 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');
diff --git a/spec/javascripts/droplab/plugins/ajax_filter_spec.js b/spec/javascripts/droplab/plugins/ajax_filter_spec.js
new file mode 100644
index 00000000000..8155d98b543
--- /dev/null
+++ b/spec/javascripts/droplab/plugins/ajax_filter_spec.js
@@ -0,0 +1,72 @@
+import AjaxCache from '~/lib/utils/ajax_cache';
+import AjaxFilter from '~/droplab/plugins/ajax_filter';
+
+describe('AjaxFilter', () => {
+ let dummyConfig;
+ const dummyData = 'dummy data';
+ let dummyList;
+
+ beforeEach(() => {
+ dummyConfig = {
+ endpoint: 'dummy endpoint',
+ searchKey: 'dummy search key',
+ };
+ dummyList = {
+ data: [],
+ list: document.createElement('div'),
+ };
+
+ AjaxFilter.hook = {
+ config: {
+ AjaxFilter: dummyConfig,
+ },
+ list: dummyList,
+ };
+ });
+
+ describe('trigger', () => {
+ let ajaxSpy;
+
+ beforeEach(() => {
+ spyOn(AjaxCache, 'retrieve').and.callFake(url => ajaxSpy(url));
+ spyOn(AjaxFilter, '_loadData');
+
+ dummyConfig.onLoadingFinished = jasmine.createSpy('spy');
+
+ const dynamicList = document.createElement('div');
+ dynamicList.dataset.dynamic = true;
+ dummyList.list.appendChild(dynamicList);
+ });
+
+ it('calls onLoadingFinished after loading data', (done) => {
+ ajaxSpy = (url) => {
+ expect(url).toBe('dummy endpoint?dummy search key=');
+ return Promise.resolve(dummyData);
+ };
+
+ AjaxFilter.trigger()
+ .then(() => {
+ expect(dummyConfig.onLoadingFinished.calls.count()).toBe(1);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not call onLoadingFinished if Ajax call fails', (done) => {
+ const dummyError = new Error('My dummy is sick! :-(');
+ ajaxSpy = (url) => {
+ expect(url).toBe('dummy endpoint?dummy search key=');
+ return Promise.reject(dummyError);
+ };
+
+ AjaxFilter.trigger()
+ .then(done.fail)
+ .catch((error) => {
+ expect(error).toBe(dummyError);
+ expect(dummyConfig.onLoadingFinished.calls.count()).toBe(0);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index f617c4bdffe..6e855530b21 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -123,4 +123,13 @@ describe('Store', () => {
expect(store.state.paginationInformation).toEqual(expectedResult);
});
});
+
+ describe('getOpenFolders', () => {
+ it('should return open folder', () => {
+ store.storeEnvironments(serverData);
+
+ store.toggleFolder(store.state.environments[1]);
+ expect(store.getOpenFolders()[0]).toEqual(store.state.environments[1]);
+ });
+ });
});
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/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
new file mode 100644
index 00000000000..554451d1bbf
--- /dev/null
+++ b/spec/javascripts/fixtures/services.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
+ let!(:service) { create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker') }
+
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('services/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'services/edit_service.html.raw' do |example|
+ get :edit,
+ namespace_id: namespace,
+ project_id: project,
+ id: service.to_param
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
new file mode 100644
index 00000000000..45909d4e70e
--- /dev/null
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -0,0 +1,199 @@
+import IntegrationSettingsForm from '~/integrations/integration_settings_form';
+
+describe('IntegrationSettingsForm', () => {
+ const FIXTURE = 'services/edit_service.html.raw';
+ preloadFixtures(FIXTURE);
+
+ beforeEach(() => {
+ loadFixtures(FIXTURE);
+ });
+
+ describe('contructor', () => {
+ let integrationSettingsForm;
+
+ beforeEach(() => {
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ spyOn(integrationSettingsForm, 'init');
+ });
+
+ it('should initialize form element refs on class object', () => {
+ // Form Reference
+ expect(integrationSettingsForm.$form).toBeDefined();
+ expect(integrationSettingsForm.$form.prop('nodeName')).toEqual('FORM');
+
+ // Form Child Elements
+ expect(integrationSettingsForm.$serviceToggle).toBeDefined();
+ expect(integrationSettingsForm.$submitBtn).toBeDefined();
+ expect(integrationSettingsForm.$submitBtnLoader).toBeDefined();
+ expect(integrationSettingsForm.$submitBtnLabel).toBeDefined();
+ });
+
+ it('should initialize form metadata on class object', () => {
+ expect(integrationSettingsForm.testEndPoint).toBeDefined();
+ expect(integrationSettingsForm.canTestService).toBeDefined();
+ });
+ });
+
+ describe('toggleServiceState', () => {
+ let integrationSettingsForm;
+
+ beforeEach(() => {
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ });
+
+ it('should remove `novalidate` attribute to form when called with `true`', () => {
+ integrationSettingsForm.toggleServiceState(true);
+
+ expect(integrationSettingsForm.$form.attr('novalidate')).not.toBeDefined();
+ });
+
+ it('should set `novalidate` attribute to form when called with `false`', () => {
+ integrationSettingsForm.toggleServiceState(false);
+
+ expect(integrationSettingsForm.$form.attr('novalidate')).toBeDefined();
+ });
+ });
+
+ describe('toggleSubmitBtnLabel', () => {
+ let integrationSettingsForm;
+
+ beforeEach(() => {
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ });
+
+ it('should set Save button label to "Test settings and save changes" when serviceActive & canTestService are `true`', () => {
+ integrationSettingsForm.canTestService = true;
+
+ integrationSettingsForm.toggleSubmitBtnLabel(true);
+ expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Test settings and save changes');
+ });
+
+ it('should set Save button label to "Save changes" when either serviceActive or canTestService (or both) is `false`', () => {
+ integrationSettingsForm.canTestService = false;
+
+ integrationSettingsForm.toggleSubmitBtnLabel(false);
+ expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
+
+ integrationSettingsForm.toggleSubmitBtnLabel(true);
+ expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
+
+ integrationSettingsForm.canTestService = true;
+
+ integrationSettingsForm.toggleSubmitBtnLabel(false);
+ expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
+ });
+ });
+
+ describe('toggleSubmitBtnState', () => {
+ let integrationSettingsForm;
+
+ beforeEach(() => {
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ });
+
+ it('should disable Save button and show loader animation when called with `true`', () => {
+ integrationSettingsForm.toggleSubmitBtnState(true);
+
+ expect(integrationSettingsForm.$submitBtn.is(':disabled')).toBeTruthy();
+ expect(integrationSettingsForm.$submitBtnLoader.hasClass('hidden')).toBeFalsy();
+ });
+
+ it('should enable Save button and hide loader animation when called with `false`', () => {
+ integrationSettingsForm.toggleSubmitBtnState(false);
+
+ expect(integrationSettingsForm.$submitBtn.is(':disabled')).toBeFalsy();
+ expect(integrationSettingsForm.$submitBtnLoader.hasClass('hidden')).toBeTruthy();
+ });
+ });
+
+ describe('testSettings', () => {
+ let integrationSettingsForm;
+ let formData;
+
+ beforeEach(() => {
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ formData = integrationSettingsForm.$form.serialize();
+ });
+
+ it('should make an ajax request with provided `formData`', () => {
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ expect($.ajax).toHaveBeenCalledWith({
+ type: 'PUT',
+ url: integrationSettingsForm.testEndPoint,
+ data: formData,
+ });
+ });
+
+ it('should show error Flash with `Save anyway` action if ajax request responds with error in test', () => {
+ const errorMessage = 'Test failed.';
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ deferred.resolve({ error: true, message: errorMessage });
+
+ const $flashContainer = $('.flash-container');
+ expect($flashContainer.find('.flash-text').text()).toEqual(errorMessage);
+ expect($flashContainer.find('.flash-action')).toBeDefined();
+ expect($flashContainer.find('.flash-action').text()).toEqual('Save anyway');
+ });
+
+ it('should submit form if ajax request responds without any error in test', () => {
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ spyOn(integrationSettingsForm.$form, 'submit');
+ deferred.resolve({ error: false });
+
+ expect(integrationSettingsForm.$form.submit).toHaveBeenCalled();
+ });
+
+ it('should submit form when clicked on `Save anyway` action of error Flash', () => {
+ const errorMessage = 'Test failed.';
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ deferred.resolve({ error: true, message: errorMessage });
+
+ const $flashAction = $('.flash-container .flash-action');
+ expect($flashAction).toBeDefined();
+
+ spyOn(integrationSettingsForm.$form, 'submit');
+ $flashAction.trigger('click');
+ expect(integrationSettingsForm.$form.submit).toHaveBeenCalled();
+ });
+
+ it('should show error Flash if ajax request failed', () => {
+ const errorMessage = 'Something went wrong on our end.';
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ deferred.reject();
+
+ expect($('.flash-container .flash-text').text()).toEqual(errorMessage);
+ });
+
+ it('should always call `toggleSubmitBtnState` with `false` once request is completed', () => {
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ spyOn(integrationSettingsForm, 'toggleSubmitBtnState');
+ deferred.reject();
+
+ expect(integrationSettingsForm.toggleSubmitBtnState).toHaveBeenCalledWith(false);
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 28c0ecc10a4..59c006aa0af 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) => {
@@ -26,14 +27,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();
});
@@ -70,4 +82,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/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 04cf0fe2bf8..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');
});
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/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/header_component_spec.js b/spec/javascripts/pipelines/header_component_spec.js
new file mode 100644
index 00000000000..b8531350e43
--- /dev/null
+++ b/spec/javascripts/pipelines/header_component_spec.js
@@ -0,0 +1,60 @@
+import Vue from 'vue';
+import headerComponent from '~/pipelines/components/header_component.vue';
+import eventHub from '~/pipelines/event_hub';
+
+describe('Pipeline details header', () => {
+ let HeaderComponent;
+ let vm;
+ let props;
+
+ beforeEach(() => {
+ HeaderComponent = Vue.extend(headerComponent);
+
+ props = {
+ pipeline: {
+ details: {
+ status: {
+ group: 'failed',
+ icon: 'ci-status-failed',
+ label: 'failed',
+ text: 'failed',
+ details_path: 'path',
+ },
+ },
+ id: 123,
+ created_at: '2017-05-08T14:57:39.781Z',
+ user: {
+ web_url: 'path',
+ name: 'Foo',
+ username: 'foobar',
+ email: 'foo@bar.com',
+ avatar_url: 'link',
+ },
+ retry_path: 'path',
+ },
+ isLoading: false,
+ };
+
+ vm = new HeaderComponent({ propsData: props }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render provided pipeline info', () => {
+ expect(
+ vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+ ).toEqual('failed Pipeline #123 triggered 3 weeks ago by Foo');
+ });
+
+ describe('action buttons', () => {
+ it('should call postAction when button action is clicked', () => {
+ eventHub.$on('headerPostAction', (action) => {
+ expect(action.path).toEqual('path');
+ });
+
+ vm.$el.querySelector('button').click();
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
new file mode 100644
index 00000000000..9fec2f61f78
--- /dev/null
+++ b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
@@ -0,0 +1,41 @@
+import Vue from 'vue';
+import PipelineMediator from '~/pipelines/pipeline_details_mediatior';
+
+describe('PipelineMdediator', () => {
+ let mediator;
+ beforeEach(() => {
+ mediator = new PipelineMediator({ endpoint: 'foo' });
+ });
+
+ it('should set defaults', () => {
+ expect(mediator.options).toEqual({ endpoint: 'foo' });
+ expect(mediator.state.isLoading).toEqual(false);
+ expect(mediator.store).toBeDefined();
+ expect(mediator.service).toBeDefined();
+ });
+
+ describe('request and store data', () => {
+ const interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({ foo: 'bar' }), {
+ status: 200,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(interceptor);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptor, interceptor);
+ });
+
+ it('should store received data', (done) => {
+ mediator.fetchPipeline();
+
+ setTimeout(() => {
+ expect(mediator.store.state.pipeline).toEqual({ foo: 'bar' });
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/pipeline_store_spec.js b/spec/javascripts/pipelines/pipeline_store_spec.js
new file mode 100644
index 00000000000..85d13445b01
--- /dev/null
+++ b/spec/javascripts/pipelines/pipeline_store_spec.js
@@ -0,0 +1,27 @@
+import PipelineStore from '~/pipelines/stores/pipeline_store';
+
+describe('Pipeline Store', () => {
+ let store;
+
+ beforeEach(() => {
+ store = new PipelineStore();
+ });
+
+ it('should set defaults', () => {
+ expect(store.state).toEqual({ pipeline: {} });
+ expect(store.state.pipeline).toEqual({});
+ });
+
+ describe('storePipeline', () => {
+ it('should store empty object if none is provided', () => {
+ store.storePipeline();
+
+ expect(store.state.pipeline).toEqual({});
+ });
+
+ it('should store received object', () => {
+ store.storePipeline({ foo: 'bar' });
+ expect(store.state.pipeline).toEqual({ foo: 'bar' });
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index d74b1281668..594a9856d2c 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -47,6 +47,7 @@ describe('Pipeline Url Component', () => {
web_url: '/',
name: 'foo',
avatar_url: '/',
+ path: '/',
},
},
};
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index 0638483e7aa..050170a54e9 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -24,6 +24,7 @@ describe('Commit component', () => {
author: {
avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1',
+ path: '/jschatz1',
username: 'jschatz1',
},
},
@@ -46,6 +47,7 @@ describe('Commit component', () => {
author: {
avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1',
+ path: '/jschatz1',
username: 'jschatz1',
},
commitIconSvg: '<svg></svg>',
@@ -81,7 +83,7 @@ describe('Commit component', () => {
it('should render a link to the author profile', () => {
expect(
component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href'),
- ).toEqual(props.author.web_url);
+ ).toEqual(props.author.path);
});
it('Should render the author avatar with title and alt attributes', () => {
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
index 1bf8916b3d0..2b51c89f311 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -33,12 +33,14 @@ describe('Header CI Component', () => {
path: 'path',
type: 'button',
cssClass: 'btn',
+ isLoading: false,
},
{
label: 'Go',
path: 'path',
type: 'link',
cssClass: 'link',
+ isLoading: false,
},
],
};
@@ -79,4 +81,13 @@ describe('Header CI Component', () => {
expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label);
expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path);
});
+
+ it('should show loading icon', (done) => {
+ vm.actions[0].isLoading = true;
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toEqual('');
+ done();
+ });
+ });
});
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/javascripts/vue_shared/components/pipelines_table_row_spec.js b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
index 286118917e8..67419cfcbea 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
+++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
@@ -76,7 +76,7 @@ describe('Pipelines Table Row', () => {
it('should render user information', () => {
expect(
component.$el.querySelector('td:nth-child(2) a:nth-child(3)').getAttribute('href'),
- ).toEqual(pipeline.user.web_url);
+ ).toEqual(pipeline.user.path);
expect(
component.$el.querySelector('td:nth-child(2) img').getAttribute('data-original-title'),
@@ -120,7 +120,7 @@ describe('Pipelines Table Row', () => {
component = buildComponent(pipeline);
const { commitAuthorLink, commitAuthorName } = findElements();
- expect(commitAuthorLink).toEqual(pipeline.commit.author.web_url);
+ expect(commitAuthorLink).toEqual(pipeline.commit.author.path);
expect(commitAuthorName).toEqual(pipeline.commit.author.username);
});
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/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/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/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index 48fc817d857..1482ef7132d 100644
--- a/spec/lib/gitlab/git/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"
-describe Gitlab::Git::EncodingHelper do
- let(:ext_class) { Class.new { extend Gitlab::Git::EncodingHelper } }
+describe Gitlab::EncodingHelper do
+ let(:ext_class) { Class.new { extend Gitlab::EncodingHelper } }
let(:binary_string) { File.read(Rails.root + "spec/fixtures/dk.png") }
describe '#encode!' do
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index 46a238b17f4..0418fc0a1e2 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -77,6 +77,17 @@ describe Gitlab::EtagCaching::Router do
expect(result).to be_blank
end
+ it 'matches the environments path' do
+ env = build_env(
+ '/my-group/my-project/environments.json'
+ )
+
+ result = described_class.match(env)
+ expect(result).to be_present
+
+ expect(result.name).to eq 'environments'
+ end
+
it 'matches pipeline#show endpoint' do
env = build_env(
'/my-group/my-project/pipelines/2.json'
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/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index cb107c6d1f9..26215381cc4 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"
describe Gitlab::Git::Repository, seed_helper: true do
- include Gitlab::Git::EncodingHelper
+ include Gitlab::EncodingHelper
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
@@ -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/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 96054c996fd..54ce8051f30 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/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 56772409989..00941aec380 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,5 +1,7 @@
+require 'spec_helper'
+
describe Gitlab::Utils, lib: true do
- delegate :to_boolean, to: :described_class
+ delegate :to_boolean, :boolean_to_yes_no, to: :described_class
describe '.to_boolean' do
it 'accepts booleans' do
@@ -30,4 +32,11 @@ describe Gitlab::Utils, lib: true do
expect(to_boolean(nil)).to be_nil
end
end
+
+ describe '.boolean_to_yes_no' do
+ it 'converts booleans to Yes or No' do
+ expect(boolean_to_yes_no(true)).to eq('Yes')
+ expect(boolean_to_yes_no(false)).to eq('No')
+ end
+ end
end
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/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
new file mode 100644
index 00000000000..a5c6170cd7d
--- /dev/null
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -0,0 +1,223 @@
+require 'spec_helper'
+require 'rake_helper'
+
+describe SystemCheck::SimpleExecutor, lib: true do
+ class SimpleCheck < SystemCheck::BaseCheck
+ set_name 'my simple check'
+
+ def check?
+ true
+ end
+ end
+
+ class OtherCheck < SystemCheck::BaseCheck
+ set_name 'other check'
+
+ def check?
+ false
+ end
+
+ def show_error
+ $stdout.puts 'this is an error text'
+ end
+ end
+
+ class SkipCheck < SystemCheck::BaseCheck
+ set_name 'skip check'
+ set_skip_reason 'this is a skip reason'
+
+ def skip?
+ true
+ end
+
+ def check?
+ raise 'should not execute this'
+ end
+ end
+
+ class MultiCheck < SystemCheck::BaseCheck
+ set_name 'multi check'
+
+ def multi_check
+ $stdout.puts 'this is a multi output check'
+ end
+
+ def check?
+ raise 'should not execute this'
+ end
+ end
+
+ class SkipMultiCheck < SystemCheck::BaseCheck
+ set_name 'skip multi check'
+
+ def skip?
+ true
+ end
+
+ def multi_check
+ raise 'should not execute this'
+ end
+ end
+
+ class RepairCheck < SystemCheck::BaseCheck
+ set_name 'repair check'
+
+ def check?
+ false
+ end
+
+ def repair!
+ true
+ end
+
+ def show_error
+ $stdout.puts 'this is an error message'
+ end
+ end
+
+ describe '#component' do
+ it 'returns stored component name' do
+ expect(subject.component).to eq('Test')
+ end
+ end
+
+ describe '#checks' do
+ before do
+ subject << SimpleCheck
+ end
+
+ it 'returns a set of classes' do
+ expect(subject.checks).to include(SimpleCheck)
+ end
+ end
+
+ describe '#<<' do
+ before do
+ subject << SimpleCheck
+ end
+
+ it 'appends a new check to the Set' do
+ subject << OtherCheck
+ stored_checks = subject.checks.to_a
+
+ expect(stored_checks.first).to eq(SimpleCheck)
+ expect(stored_checks.last).to eq(OtherCheck)
+ end
+
+ it 'inserts unique itens only' do
+ subject << SimpleCheck
+
+ expect(subject.checks.size).to eq(1)
+ end
+ end
+
+ subject { described_class.new('Test') }
+
+ describe '#execute' do
+ before do
+ silence_output
+
+ subject << SimpleCheck
+ subject << OtherCheck
+ end
+
+ it 'runs included checks' do
+ expect(subject).to receive(:run_check).with(SimpleCheck)
+ expect(subject).to receive(:run_check).with(OtherCheck)
+
+ subject.execute
+ end
+ end
+
+ describe '#run_check' do
+ it 'prints check name' do
+ expect(SimpleCheck).to receive(:display_name).and_call_original
+ expect { subject.run_check(SimpleCheck) }.to output(/my simple check/).to_stdout
+ end
+
+ context 'when check pass' do
+ it 'prints yes' do
+ expect_any_instance_of(SimpleCheck).to receive(:check?).and_call_original
+ expect { subject.run_check(SimpleCheck) }.to output(/ \.\.\. yes/).to_stdout
+ end
+ end
+
+ context 'when check fails' do
+ it 'prints no' do
+ expect_any_instance_of(OtherCheck).to receive(:check?).and_call_original
+ expect { subject.run_check(OtherCheck) }.to output(/ \.\.\. no/).to_stdout
+ end
+
+ it 'displays error message from #show_error' do
+ expect_any_instance_of(OtherCheck).to receive(:show_error).and_call_original
+ expect { subject.run_check(OtherCheck) }.to output(/this is an error text/).to_stdout
+ end
+
+ context 'when check implements #repair!' do
+ it 'executes #repair!' do
+ expect_any_instance_of(RepairCheck).to receive(:repair!)
+
+ subject.run_check(RepairCheck)
+ end
+
+ context 'when repair succeeds' do
+ it 'does not execute #show_error' do
+ expect_any_instance_of(RepairCheck).to receive(:repair!).and_call_original
+ expect_any_instance_of(RepairCheck).not_to receive(:show_error)
+
+ subject.run_check(RepairCheck)
+ end
+ end
+
+ context 'when repair fails' do
+ it 'does not execute #show_error' do
+ expect_any_instance_of(RepairCheck).to receive(:repair!) { false }
+ expect_any_instance_of(RepairCheck).to receive(:show_error)
+
+ subject.run_check(RepairCheck)
+ end
+ end
+ end
+ end
+
+ context 'when check implements skip?' do
+ it 'executes #skip? method' do
+ expect_any_instance_of(SkipCheck).to receive(:skip?).and_call_original
+
+ subject.run_check(SkipCheck)
+ end
+
+ it 'displays #skip_reason' do
+ expect { subject.run_check(SkipCheck) }.to output(/this is a skip reason/).to_stdout
+ end
+
+ it 'does not execute #check when #skip? is true' do
+ expect_any_instance_of(SkipCheck).not_to receive(:check?)
+
+ subject.run_check(SkipCheck)
+ end
+ end
+
+ context 'when implements a #multi_check' do
+ it 'executes #multi_check method' do
+ expect_any_instance_of(MultiCheck).to receive(:multi_check)
+
+ subject.run_check(MultiCheck)
+ end
+
+ it 'does not execute #check method' do
+ expect_any_instance_of(MultiCheck).not_to receive(:check)
+
+ subject.run_check(MultiCheck)
+ end
+
+ context 'when check implements #skip?' do
+ it 'executes #skip? method' do
+ expect_any_instance_of(SkipMultiCheck).to receive(:skip?).and_call_original
+
+ subject.run_check(SkipMultiCheck)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/system_check_spec.rb b/spec/lib/system_check_spec.rb
new file mode 100644
index 00000000000..23d9beddb08
--- /dev/null
+++ b/spec/lib/system_check_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+require 'rake_helper'
+
+describe SystemCheck, lib: true do
+ class SimpleCheck < SystemCheck::BaseCheck
+ def check?
+ true
+ end
+ end
+
+ class OtherCheck < SystemCheck::BaseCheck
+ def check?
+ false
+ end
+ end
+
+ before do
+ silence_output
+ end
+
+ describe '.run' do
+ subject { SystemCheck }
+
+ it 'detects execution of SimpleCheck' do
+ is_expected.to execute_check(SimpleCheck)
+
+ subject.run('Test', [SimpleCheck])
+ end
+
+ it 'detects exclusion of OtherCheck in execution' do
+ is_expected.not_to execute_check(OtherCheck)
+
+ subject.run('Test', [SimpleCheck])
+ end
+ end
+end
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
new file mode 100644
index 00000000000..50f4bbda001
--- /dev/null
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -0,0 +1,117 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170523083112_migrate_old_artifacts.rb')
+
+describe MigrateOldArtifacts do
+ let(:migration) { described_class.new }
+ let!(:directory) { Dir.mktmpdir }
+
+ before do
+ allow(Gitlab.config.artifacts).to receive(:path).and_return(directory)
+ end
+
+ after do
+ FileUtils.remove_entry_secure(directory)
+ end
+
+ context 'with migratable data' do
+ let(:project1) { create(:empty_project, ci_id: 2) }
+ let(:project2) { create(:empty_project, ci_id: 3) }
+ let(:project3) { create(:empty_project) }
+
+ let(:pipeline1) { create(:ci_empty_pipeline, project: project1) }
+ let(:pipeline2) { create(:ci_empty_pipeline, project: project2) }
+ let(:pipeline3) { create(:ci_empty_pipeline, project: project3) }
+
+ let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) }
+ let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) }
+ let!(:build2) { create(:ci_build, :artifacts, pipeline: pipeline2) }
+ let!(:build3) { create(:ci_build, :artifacts, pipeline: pipeline3) }
+
+ before do
+ store_artifacts_in_legacy_path(build_with_legacy_artifacts)
+ end
+
+ it "legacy artifacts are not accessible" do
+ expect(build_with_legacy_artifacts.artifacts?).to be_falsey
+ end
+
+ it "legacy artifacts are set" do
+ expect(build_with_legacy_artifacts.artifacts_file_identifier).not_to be_nil
+ end
+
+ describe '#min_id' do
+ subject { migration.send(:min_id) }
+
+ it 'returns the newest build for which ci_id is not defined' do
+ is_expected.to eq(build3.id)
+ end
+ end
+
+ describe '#builds_with_artifacts' do
+ subject { migration.send(:builds_with_artifacts).map(&:id) }
+
+ it 'returns a list of builds that has artifacts and could be migrated' do
+ is_expected.to contain_exactly(build_with_legacy_artifacts.id, build2.id)
+ end
+ end
+
+ describe '#up' do
+ context 'when migrating artifacts' do
+ before do
+ migration.up
+ end
+
+ it 'all files do have artifacts' do
+ Ci::Build.with_artifacts do |build|
+ expect(build).to have_artifacts
+ end
+ end
+
+ it 'artifacts are no longer present on legacy path' do
+ expect(File.exist?(legacy_path(build_with_legacy_artifacts))).to eq(false)
+ end
+ end
+
+ context 'when there are aritfacts in old and new directory' do
+ before do
+ store_artifacts_in_legacy_path(build2)
+
+ migration.up
+ end
+
+ it 'does not move old files' do
+ expect(File.exist?(legacy_path(build2))).to eq(true)
+ end
+ end
+ end
+
+ private
+
+ def store_artifacts_in_legacy_path(build)
+ FileUtils.mkdir_p(legacy_path(build))
+
+ FileUtils.copy(
+ Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
+ File.join(legacy_path(build), "ci_build_artifacts.zip"))
+
+ FileUtils.copy(
+ Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
+ File.join(legacy_path(build), "ci_build_artifacts_metadata.gz"))
+
+ build.update_columns(
+ artifacts_file: 'ci_build_artifacts.zip',
+ artifacts_metadata: 'ci_build_artifacts_metadata.gz')
+
+ build.reload
+ end
+
+ def legacy_path(build)
+ File.join(directory,
+ build.created_at.utc.strftime('%Y_%m'),
+ build.project.ci_id.to_s,
+ build.id.to_s)
+ end
+ end
+end
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/build_spec.rb b/spec/models/ci/build_spec.rb
index e971b4bc3f9..e2406290c6c 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1215,16 +1215,49 @@ describe Ci::Build, :models do
it { is_expected.to include(tag_variable) }
end
- context 'when secure variable is defined' do
- let(:secure_variable) do
+ context 'when secret variable is defined' do
+ let(:secret_variable) do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end
before do
- build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+ create(:ci_variable,
+ secret_variable.slice(:key, :value).merge(project: project))
end
- it { is_expected.to include(secure_variable) }
+ it { is_expected.to include(secret_variable) }
+ end
+
+ context 'when protected variable is defined' do
+ let(:protected_variable) do
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ end
+
+ before do
+ create(:ci_variable,
+ :protected,
+ protected_variable.slice(:key, :value).merge(project: project))
+ end
+
+ context 'when the branch is protected' do
+ before do
+ create(:protected_branch, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the tag is protected' do
+ before do
+ create(:protected_tag, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the ref is not protected' do
+ it { is_expected.not_to include(protected_variable) }
+ end
end
context 'when build is for triggers' do
@@ -1346,15 +1379,30 @@ describe Ci::Build, :models do
end
context 'returns variables in valid order' do
+ let(:build_pre_var) { { key: 'build', value: 'value' } }
+ let(:project_pre_var) { { key: 'project', value: 'value' } }
+ let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } }
+ let(:build_yaml_var) { { key: 'yaml', value: 'value' } }
+
before do
- allow(build).to receive(:predefined_variables) { ['predefined'] }
- allow(project).to receive(:predefined_variables) { ['project'] }
- allow(pipeline).to receive(:predefined_variables) { ['pipeline'] }
- allow(build).to receive(:yaml_variables) { ['yaml'] }
- allow(project).to receive(:secret_variables) { ['secret'] }
+ allow(build).to receive(:predefined_variables) { [build_pre_var] }
+ allow(project).to receive(:predefined_variables) { [project_pre_var] }
+ allow(pipeline).to receive(:predefined_variables) { [pipeline_pre_var] }
+ allow(build).to receive(:yaml_variables) { [build_yaml_var] }
+
+ allow(project).to receive(:secret_variables_for).with(build.ref) do
+ [create(:ci_variable, key: 'secret', value: 'value')]
+ end
end
- it { is_expected.to eq(%w[predefined project pipeline yaml secret]) }
+ it do
+ is_expected.to eq(
+ [build_pre_var,
+ project_pre_var,
+ pipeline_pre_var,
+ build_yaml_var,
+ { key: 'secret', value: 'value', public: false }])
+ end
end
end
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/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index fe8c52d5353..077b10227d7 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -12,11 +12,33 @@ describe Ci::Variable, models: true do
it { is_expected.not_to allow_value('foo bar').for(:key) }
it { is_expected.not_to allow_value('foo/bar').for(:key) }
- before :each do
- subject.value = secret_value
+ describe '.unprotected' do
+ subject { described_class.unprotected }
+
+ context 'when variable is protected' do
+ before do
+ create(:ci_variable, :protected)
+ end
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when variable is not protected' do
+ let(:variable) { create(:ci_variable, protected: false) }
+
+ it 'returns the variable' do
+ is_expected.to contain_exactly(variable)
+ end
+ end
end
describe '#value' do
+ before do
+ subject.value = secret_value
+ end
+
it 'stores the encrypted value' do
expect(subject.encrypted_value).not_to be_nil
end
@@ -36,4 +58,11 @@ describe Ci::Variable, models: true do
to raise_error(OpenSSL::Cipher::CipherError, 'bad decrypt')
end
end
+
+ describe '#to_runner_variable' do
+ it 'returns a hash for the runner' do
+ expect(subject.to_runner_variable)
+ .to eq(key: subject.key, value: subject.value, public: false)
+ end
+ end
end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 4bda7d4314a..6f0d2db23c7 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -16,6 +16,19 @@ describe Deployment, models: true do
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to validate_presence_of(:sha) }
+ describe 'after_create callbacks' do
+ let(:environment) { create(:environment) }
+ let(:store) { Gitlab::EtagCaching::Store.new }
+
+ it 'invalidates the environment etag cache' do
+ old_value = store.get(environment.etag_cache_key)
+
+ create(:deployment, environment: environment)
+
+ expect(store.get(environment.etag_cache_key)).not_to eq(old_value)
+ end
+ end
+
describe '#includes_commit?' do
let(:project) { create(:project, :repository) }
let(:environment) { create(:environment, project: project) }
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 96f075d4f7d..297c2108dc2 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -160,12 +160,6 @@ describe DiffNote, models: true do
context "when noteable is a commit" do
let(:diff_note) { create(:diff_note_on_commit, project: project, position: position) }
- it "doesn't use the DiffPositionUpdateService" do
- expect(Notes::DiffPositionUpdateService).not_to receive(:new)
-
- diff_note
- end
-
it "doesn't update the position" do
diff_note
@@ -178,12 +172,6 @@ describe DiffNote, models: true do
let(:diff_note) { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
context "when the note is active" do
- it "doesn't use the DiffPositionUpdateService" do
- expect(Notes::DiffPositionUpdateService).not_to receive(:new)
-
- diff_note
- end
-
it "doesn't update the position" do
diff_note
@@ -197,18 +185,11 @@ describe DiffNote, models: true do
allow(merge_request).to receive(:diff_refs).and_return(commit.diff_refs)
end
- it "uses the DiffPositionUpdateService" do
- service = instance_double("Notes::DiffPositionUpdateService")
- expect(Notes::DiffPositionUpdateService).to receive(:new).with(
- project,
- nil,
- old_diff_refs: position.diff_refs,
- new_diff_refs: commit.diff_refs,
- paths: [path]
- ).and_return(service)
- expect(service).to receive(:execute)
-
+ it "updates the position" do
diff_note
+
+ expect(diff_note.original_position).to eq(position)
+ expect(diff_note.position).not_to eq(position)
end
end
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 12519de8636..fe69c8e351d 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Environment, models: true do
- let(:project) { create(:empty_project) }
+ set(:project) { create(:empty_project) }
subject(:environment) { create(:environment, project: project) }
it { is_expected.to belong_to(:project) }
@@ -34,6 +34,26 @@ describe Environment, models: true do
end
end
+ describe 'state machine' do
+ it 'invalidates the cache after a change' do
+ expect(environment).to receive(:expire_etag_cache)
+
+ environment.stop
+ end
+ end
+
+ describe '#expire_etag_cache' do
+ let(:store) { Gitlab::EtagCaching::Store.new }
+
+ it 'changes the cached value' do
+ old_value = store.get(environment.etag_cache_key)
+
+ environment.stop
+
+ expect(store.get(environment.etag_cache_key)).not_to eq(old_value)
+ end
+ end
+
describe '#nullify_external_url' do
it 'replaces a blank url with nil' do
env = build(:environment, external_url: "")
@@ -227,7 +247,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/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 712470d6bf5..060754fab63 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
@@ -1178,7 +1178,7 @@ describe MergeRequest, models: true do
end
describe "#reload_diff" do
- let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) }
+ let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
let(:commit) { subject.project.commit(sample_commit.id) }
@@ -1197,7 +1197,7 @@ describe MergeRequest, models: true do
subject.reload_diff
end
- it "updates diff note positions" do
+ it "updates diff discussion positions" do
old_diff_refs = subject.diff_refs
# Update merge_request_diff so that #diff_refs will return commit.diff_refs
@@ -1211,15 +1211,15 @@ describe MergeRequest, models: true do
subject.merge_request_diff(true)
end
- expect(Notes::DiffPositionUpdateService).to receive(:new).with(
+ expect(Discussions::UpdateDiffPositionService).to receive(:new).with(
subject.project,
subject.author,
old_diff_refs: old_diff_refs,
new_diff_refs: commit.diff_refs,
- paths: note.position.paths
+ paths: discussion.position.paths
).and_call_original
- expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note)
+ expect_any_instance_of(Discussions::UpdateDiffPositionService).to receive(:execute).with(discussion).and_call_original
expect_any_instance_of(DiffNote).to receive(:save).once
subject.reload_diff(subject.author)
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 1920b5bf42b..0ee050196e4 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -69,41 +69,6 @@ describe JiraService, models: true do
end
end
- describe '#can_test?' do
- let(:jira_service) { described_class.new }
-
- it 'returns false if username is blank' do
- allow(jira_service).to receive_messages(
- url: 'http://jira.example.com',
- username: '',
- password: '12345678'
- )
-
- expect(jira_service.can_test?).to be_falsy
- end
-
- it 'returns false if password is blank' do
- allow(jira_service).to receive_messages(
- url: 'http://jira.example.com',
- username: 'tester',
- password: ''
- )
-
- expect(jira_service.can_test?).to be_falsy
- end
-
- it 'returns true if password and username are present' do
- jira_service = described_class.new
- allow(jira_service).to receive_messages(
- url: 'http://jira.example.com',
- username: 'tester',
- password: '12345678'
- )
-
- expect(jira_service.can_test?).to be_truthy
- end
- end
-
describe '#close_issue' do
let(:custom_base_url) { 'http://custom_url' }
let(:user) { create(:user) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 2ef3d5654d5..86ab2550bfb 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) }
@@ -1735,6 +1749,90 @@ describe Project, models: true do
end
end
+ describe '#secret_variables_for' do
+ let(:project) { create(:empty_project) }
+
+ let!(:secret_variable) do
+ create(:ci_variable, value: 'secret', project: project)
+ end
+
+ let!(:protected_variable) do
+ create(:ci_variable, :protected, value: 'protected', project: project)
+ end
+
+ subject { project.secret_variables_for('ref') }
+
+ shared_examples 'ref is protected' do
+ it 'contains all the variables' do
+ is_expected.to contain_exactly(secret_variable, protected_variable)
+ end
+ end
+
+ context 'when the ref is not protected' do
+ before do
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+ end
+
+ it 'contains only the secret variables' do
+ is_expected.to contain_exactly(secret_variable)
+ end
+ end
+
+ context 'when the ref is a protected branch' do
+ before do
+ create(:protected_branch, name: 'ref', project: project)
+ end
+
+ it_behaves_like 'ref is protected'
+ end
+
+ context 'when the ref is a protected tag' do
+ before do
+ create(:protected_tag, name: 'ref', project: project)
+ end
+
+ it_behaves_like 'ref is protected'
+ end
+ end
+
+ describe '#protected_for?' do
+ let(:project) { create(:empty_project) }
+
+ subject { project.protected_for?('ref') }
+
+ context 'when the ref is not protected' do
+ before do
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+ end
+
+ it 'returns false' do
+ is_expected.to be_falsey
+ end
+ end
+
+ context 'when the ref is a protected branch' do
+ before do
+ create(:protected_branch, name: 'ref', project: project)
+ end
+
+ it 'returns true' do
+ is_expected.to be_truthy
+ end
+ end
+
+ context 'when the ref is a protected tag' do
+ before do
+ create(:protected_tag, name: 'ref', project: project)
+ end
+
+ it 'returns true' do
+ is_expected.to be_truthy
+ end
+ end
+ end
+
describe '#update_project_statistics' do
let(:project) { create(:empty_project) }
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index fb2d5f60009..362565506e5 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -327,69 +327,114 @@ describe ProjectTeam, models: true do
end
end
- shared_examples_for "#max_member_access_for_users" do |enable_request_store|
- describe "#max_member_access_for_users" do
+ shared_examples 'max member access for users' do
+ let(:project) { create(:project) }
+ let(:group) { create(:group) }
+ let(:second_group) { create(:group) }
+
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+
+ let(:promoted_guest) { create(:user) }
+
+ let(:group_developer) { create(:user) }
+ let(:second_developer) { create(:user) }
+
+ let(:user_without_access) { create(:user) }
+ let(:second_user_without_access) { create(:user) }
+
+ let(:users) do
+ [master, reporter, promoted_guest, guest, group_developer, second_developer, user_without_access].map(&:id)
+ end
+
+ let(:expected) do
+ {
+ master.id => Gitlab::Access::MASTER,
+ reporter.id => Gitlab::Access::REPORTER,
+ promoted_guest.id => Gitlab::Access::DEVELOPER,
+ guest.id => Gitlab::Access::GUEST,
+ group_developer.id => Gitlab::Access::DEVELOPER,
+ second_developer.id => Gitlab::Access::MASTER,
+ user_without_access.id => Gitlab::Access::NO_ACCESS
+ }
+ end
+
+ before do
+ project.add_master(master)
+ project.add_reporter(reporter)
+ project.add_guest(promoted_guest)
+ project.add_guest(guest)
+
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::DEVELOPER
+ )
+
+ group.add_master(promoted_guest)
+ group.add_developer(group_developer)
+ group.add_developer(second_developer)
+
+ project.project_group_links.create(
+ group: second_group,
+ group_access: Gitlab::Access::MASTER
+ )
+
+ second_group.add_master(second_developer)
+ end
+
+ it 'returns correct roles for different users' do
+ expect(project.team.max_member_access_for_user_ids(users)).to eq(expected)
+ end
+ end
+
+ describe '#max_member_access_for_user_ids' do
+ context 'with RequestStore enabled' do
before do
- RequestStore.begin! if enable_request_store
+ RequestStore.begin!
end
after do
- if enable_request_store
- RequestStore.end!
- RequestStore.clear!
- end
+ RequestStore.end!
+ RequestStore.clear!
end
- it 'returns correct roles for different users' do
- master = create(:user)
- reporter = create(:user)
- promoted_guest = create(:user)
- guest = create(:user)
- project = create(:empty_project)
+ include_examples 'max member access for users'
- project.add_master(master)
- project.add_reporter(reporter)
- project.add_guest(promoted_guest)
- project.add_guest(guest)
+ def access_levels(users)
+ project.team.max_member_access_for_user_ids(users)
+ end
+
+ it 'does not perform extra queries when asked for users who have already been found' do
+ access_levels(users)
+
+ expect { access_levels(users) }.not_to exceed_query_limit(0)
- group = create(:group)
- group_developer = create(:user)
- second_developer = create(:user)
- project.project_group_links.create(
- group: group,
- group_access: Gitlab::Access::DEVELOPER)
-
- group.add_master(promoted_guest)
- group.add_developer(group_developer)
- group.add_developer(second_developer)
-
- second_group = create(:group)
- project.project_group_links.create(
- group: second_group,
- group_access: Gitlab::Access::MASTER)
- second_group.add_master(second_developer)
-
- users = [master, reporter, promoted_guest, guest, group_developer, second_developer].map(&:id)
-
- expected = {
- master.id => Gitlab::Access::MASTER,
- reporter.id => Gitlab::Access::REPORTER,
- promoted_guest.id => Gitlab::Access::DEVELOPER,
- guest.id => Gitlab::Access::GUEST,
- group_developer.id => Gitlab::Access::DEVELOPER,
- second_developer.id => Gitlab::Access::MASTER
- }
-
- expect(project.team.max_member_access_for_user_ids(users)).to eq(expected)
+ expect(access_levels(users)).to eq(expected)
end
- end
- end
- describe '#max_member_access_for_users with RequestStore' do
- it_behaves_like "#max_member_access_for_users", true
- end
+ it 'only requests the extra users when uncached users are passed' do
+ new_user = create(:user)
+ second_new_user = create(:user)
+ all_users = users + [new_user.id, second_new_user.id]
+
+ expected_all = expected.merge(new_user.id => Gitlab::Access::NO_ACCESS,
+ second_new_user.id => Gitlab::Access::NO_ACCESS)
+
+ access_levels(users)
- describe '#max_member_access_for_users without RequestStore' do
- it_behaves_like "#max_member_access_for_users", false
+ queries = ActiveRecord::QueryRecorder.new { access_levels(all_users) }
+
+ expect(queries.count).to eq(1)
+ expect(queries.log_message).to match(/\W#{new_user.id}\W/)
+ expect(queries.log_message).to match(/\W#{second_new_user.id}\W/)
+ expect(queries.log_message).not_to match(/\W#{promoted_guest.id}\W/)
+ expect(access_levels(all_users)).to eq(expected_all)
+ end
+ end
+
+ context 'with RequestStore disabled' do
+ include_examples 'max member access for users'
+ end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 9edf34b05ad..1c3541da44f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -22,7 +22,7 @@ describe User, models: true do
it { is_expected.to have_many(:deploy_keys).dependent(:destroy) }
it { is_expected.to have_many(:events).dependent(:destroy) }
it { is_expected.to have_many(:recent_events).class_name('Event') }
- it { is_expected.to have_many(:issues).dependent(:restrict_with_exception) }
+ it { is_expected.to have_many(:issues).dependent(:destroy) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
it { is_expected.to have_many(:identities).dependent(:destroy) }
@@ -1496,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) }
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 b84361d3abd..b0c265b6453 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -485,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)
@@ -495,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/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/projects_spec.rb b/spec/requests/api/projects_spec.rb
index f95a287a184..40bfc0c636b 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -316,15 +316,15 @@ describe API::Projects do
expect(project.path).to eq('foo_project')
end
- it 'creates new project name and path and returns 201' do
- expect { post api('/projects', user), path: 'foo-Project', name: 'Foo Project' }.
+ it 'creates new project with name and path and returns 201' do
+ expect { post api('/projects', user), path: 'path-project-Foo', name: 'Foo Project' }.
to change { Project.count }.by(1)
expect(response).to have_http_status(201)
project = Project.first
expect(project.name).to eq('Foo Project')
- expect(project.path).to eq('foo-Project')
+ expect(project.path).to eq('path-project-Foo')
end
it 'creates last project before reaching project limit' do
@@ -390,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
@@ -462,9 +470,25 @@ describe API::Projects do
before { project }
before { admin }
- it 'creates new project without path and return 201' do
- expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1)
+ it 'creates new project without path but with name and return 201' do
+ expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change {Project.count}.by(1)
+ expect(response).to have_http_status(201)
+
+ project = Project.first
+
+ expect(project.name).to eq('Foo Project')
+ expect(project.path).to eq('foo-project')
+ end
+
+ it 'creates new project with name and path and returns 201' do
+ expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }.
+ to change { Project.count }.by(1)
expect(response).to have_http_status(201)
+
+ project = Project.first
+
+ expect(project.name).to eq('Foo Project')
+ expect(project.path).to eq('path-project-Foo')
end
it 'responds with 400 on failure and not project' do
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index 386f60065ad..4a4a5dc5c7c 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -386,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)
@@ -396,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/deploy_keys_spec.rb b/spec/requests/api/v3/deploy_keys_spec.rb
index b61b2b618a6..94f4d93a8dc 100644
--- a/spec/requests/api/v3/deploy_keys_spec.rb
+++ b/spec/requests/api/v3/deploy_keys_spec.rb
@@ -105,6 +105,15 @@ describe API::V3::DeployKeys do
expect(response).to have_http_status(201)
end
+
+ it 'accepts can_push parameter' do
+ key_attrs = attributes_for :write_access_key
+
+ post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs
+
+ expect(response).to have_http_status(201)
+ expect(json_response['can_push']).to eq(true)
+ end
end
describe "DELETE /projects/:id/#{path}/:key_id" do
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 63d6d3001ac..83673864fe7 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -42,6 +42,7 @@ describe API::Variables do
expect(response).to have_http_status(200)
expect(json_response['value']).to eq(variable.value)
+ expect(json_response['protected']).to eq(variable.protected?)
end
it 'responds with 404 Not Found if requesting non-existing variable' do
@@ -72,12 +73,13 @@ describe API::Variables do
context 'authorized user with proper permissions' do
it 'creates variable' do
expect do
- post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
+ post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true
end.to change{project.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['protected']).to be_truthy
end
it 'does not allow to duplicate variable key' do
@@ -112,13 +114,14 @@ describe API::Variables do
initial_variable = project.variables.first
value_before = initial_variable.value
- put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP'
+ put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP', protected: true
updated_variable = project.variables.first
expect(response).to have_http_status(200)
expect(value_before).to eq(variable.value)
expect(updated_variable.value).to eq('VALUE_1_UP')
+ expect(updated_variable).to be_protected
end
it 'responds with 404 Not Found if requesting non-existing variable' do
diff --git a/spec/rubocop/cop/activerecord_serialize_spec.rb b/spec/rubocop/cop/activerecord_serialize_spec.rb
new file mode 100644
index 00000000000..a303b16d264
--- /dev/null
+++ b/spec/rubocop/cop/activerecord_serialize_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/activerecord_serialize'
+
+describe RuboCop::Cop::ActiverecordSerialize do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'inside the app/models directory' do
+ it 'registers an offense when serialize is used' do
+ allow(cop).to receive(:in_models?).and_return(true)
+
+ inspect_source(cop, 'serialize :foo')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+
+ context 'outside the app/models directory' do
+ it 'does nothing' do
+ allow(cop).to receive(:in_models?).and_return(false)
+
+ inspect_source(cop, 'serialize :foo')
+
+ expect(cop.offenses).to be_empty
+ end
+ 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/serializers/user_entity_spec.rb b/spec/serializers/user_entity_spec.rb
index c5d11cbcf5e..cd778e49107 100644
--- a/spec/serializers/user_entity_spec.rb
+++ b/spec/serializers/user_entity_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe UserEntity do
+ include Gitlab::Routing
+
let(:entity) { described_class.new(user) }
let(:user) { create(:user) }
subject { entity.as_json }
@@ -20,4 +22,8 @@ describe UserEntity do
it 'does not expose 2FA OTPs' do
expect(subject).not_to include(/otp/)
end
+
+ it 'exposes user path' do
+ expect(subject[:path]).to eq user_path(user)
+ end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 030912b9f45..06fbd7bad90 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,6 +30,7 @@ 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')
@@ -71,10 +72,11 @@ describe Ci::CreatePipelineService, services: true do
end
end
- context 'when merge request head commit sha does not match pipeline sha' do
+ context 'when the pipeline is not the latest for the branch' 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))
+
+ allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false)
pipeline
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/notes/diff_position_update_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb
index 380c296fd3a..177e32e13bd 100644
--- a/spec/services/notes/diff_position_update_service_spec.rb
+++ b/spec/services/discussions/update_diff_position_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Notes::DiffPositionUpdateService, services: true do
+describe Discussions::UpdateDiffPositionService, services: true do
let(:project) { create(:project, :repository) }
let(:current_user) { project.owner }
let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") }
@@ -138,7 +138,7 @@ describe Notes::DiffPositionUpdateService, services: true do
# .. ..
describe "#execute" do
- let(:note) { create(:diff_note_on_merge_request, project: project, position: old_position) }
+ let(:discussion) { create(:diff_note_on_merge_request, project: project, position: old_position).to_discussion }
let(:old_position) do
Gitlab::Diff::Position.new(
@@ -154,11 +154,11 @@ describe Notes::DiffPositionUpdateService, services: true do
let(:line) { 16 }
it "updates the position" do
- subject.execute(note)
+ subject.execute(discussion)
- expect(note.original_position).to eq(old_position)
- expect(note.position).not_to eq(old_position)
- expect(note.position.new_line).to eq(22)
+ expect(discussion.original_position).to eq(old_position)
+ expect(discussion.position).not_to eq(old_position)
+ expect(discussion.position.new_line).to eq(22)
end
end
@@ -166,27 +166,27 @@ describe Notes::DiffPositionUpdateService, services: true do
let(:line) { 9 }
it "doesn't update the position" do
- subject.execute(note)
+ subject.execute(discussion)
- expect(note.original_position).to eq(old_position)
- expect(note.position).to eq(old_position)
+ expect(discussion.original_position).to eq(old_position)
+ expect(discussion.position).to eq(old_position)
end
it 'sets the change position' do
- subject.execute(note)
+ subject.execute(discussion)
- change_position = note.change_position
+ change_position = discussion.change_position
expect(change_position.start_sha).to eq(old_diff_refs.head_sha)
expect(change_position.head_sha).to eq(new_diff_refs.head_sha)
expect(change_position.old_line).to eq(9)
expect(change_position.new_line).to be_nil
end
- it 'creates a system note' do
+ it 'creates a system discussion' do
expect(SystemNoteService).to receive(:diff_discussion_outdated).with(
- note.to_discussion, project, current_user, instance_of(Gitlab::Diff::Position))
+ discussion, project, current_user, instance_of(Gitlab::Diff::Position))
- subject.execute(note)
+ subject.execute(discussion)
end
end
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 9f5a8beac16..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 )
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/gravatar_service_spec.rb b/spec/services/gravatar_service_spec.rb
new file mode 100644
index 00000000000..8c4ad8c7a3e
--- /dev/null
+++ b/spec/services/gravatar_service_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe GravatarService, service: true do
+ describe '#execute' do
+ let(:url) { 'http://example.com/avatar?hash=%{hash}&size=%{size}&email=%{email}&username=%{username}' }
+
+ before do
+ allow(Gitlab.config.gravatar).to receive(:plain_url).and_return(url)
+ end
+
+ it 'replaces the placeholders' do
+ avatar_url = described_class.new.execute('user@example.com', 100, 2, username: 'user')
+
+ expect(avatar_url).to include("hash=#{Digest::MD5.hexdigest('user@example.com')}")
+ expect(avatar_url).to include("size=200")
+ expect(avatar_url).to include("email=user%40example.com")
+ expect(avatar_url).to include("username=user")
+ end
+ end
+end
diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
index 19e8d5cc5f1..c77e6e9cd50 100644
--- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
@@ -26,6 +26,10 @@ describe MergeRequests::Conflicts::ResolveService do
describe '#execute' do
let(:service) { described_class.new(merge_request) }
+ def blob_content(project, ref, path)
+ project.repository.blob_at(ref, path).data
+ end
+
context 'with section params' do
let(:params) do
{
@@ -66,6 +70,35 @@ describe MergeRequests::Conflicts::ResolveService do
end
end
+ context 'when some files have trailing newlines' do
+ let!(:source_head) do
+ branch = 'conflict-resolvable'
+ path = 'files/ruby/popen.rb'
+ popen_content = blob_content(project, branch, path)
+
+ project.repository.update_file(
+ user,
+ path,
+ popen_content.chomp("\n"),
+ message: 'Remove trailing newline from popen.rb',
+ branch_name: branch
+ )
+ end
+
+ before do
+ service.execute(user, params)
+ end
+
+ it 'preserves trailing newlines from our side of the conflicts' do
+ head_sha = merge_request.source_branch_head.sha
+ popen_content = blob_content(project, head_sha, 'files/ruby/popen.rb')
+ regex_content = blob_content(project, head_sha, 'files/ruby/regex.rb')
+
+ expect(popen_content).not_to end_with("\n")
+ expect(regex_content).to end_with("\n")
+ end
+ end
+
context 'when the source project is a fork and does not contain the HEAD of the target branch' do
let!(:target_head) do
project.repository.create_file(
@@ -142,10 +175,13 @@ describe MergeRequests::Conflicts::ResolveService do
end
it 'sets the content to the content given' do
- blob = merge_request.source_project.repository.blob_at(merge_request.source_branch_head.sha,
- 'files/ruby/popen.rb')
+ blob = blob_content(
+ merge_request.source_project,
+ merge_request.source_branch_head.sha,
+ 'files/ruby/popen.rb'
+ )
- expect(blob.data).to eq(popen_content)
+ expect(blob).to eq(popen_content)
end
end
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index de37a61e388..5409f67c091 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -147,16 +147,22 @@ describe Users::DestroyService, services: true do
end
context "migrating associated records" do
+ let!(:issue) { create(:issue, author: user) }
+
it 'delegates to the `MigrateToGhostUser` service to move associated records to the ghost user' do
- expect_any_instance_of(Users::MigrateToGhostUserService).to receive(:execute).once
+ expect_any_instance_of(Users::MigrateToGhostUserService).to receive(:execute).once.and_call_original
service.execute(user)
+
+ expect(issue.reload.author).to be_ghost
end
it 'does not run `MigrateToGhostUser` if hard_delete option is given' do
expect_any_instance_of(Users::MigrateToGhostUserService).not_to receive(:execute)
service.execute(user, hard_delete: true)
+
+ expect(Issue.exists?(issue.id)).to be_falsy
end
end
end
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
index 5341ba3d261..054e28ae7b0 100644
--- a/spec/services/wiki_pages/create_service_spec.rb
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe WikiPages::CreateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
+
let(:opts) do
{
title: 'Title',
@@ -10,27 +11,28 @@ describe WikiPages::CreateService, services: true do
format: 'markdown'
}
end
- let(:service) { described_class.new(project, user, opts) }
+
+ subject(:service) { described_class.new(project, user, opts) }
+
+ before do
+ project.add_developer(user)
+ end
describe '#execute' do
- context "valid params" do
- before do
- allow(service).to receive(:execute_hooks)
- project.add_master(user)
- end
-
- subject { service.execute }
-
- it 'creates a valid wiki page' do
- is_expected.to be_valid
- expect(subject.title).to eq(opts[:title])
- expect(subject.content).to eq(opts[:content])
- expect(subject.format).to eq(opts[:format].to_sym)
- end
-
- it 'executes webhooks' do
- expect(service).to have_received(:execute_hooks).once.with(subject, 'create')
- end
+ it 'creates wiki page with valid attributes' do
+ page = service.execute
+
+ expect(page).to be_valid
+ expect(page.title).to eq(opts[:title])
+ expect(page.content).to eq(opts[:content])
+ expect(page.format).to eq(opts[:format].to_sym)
+ end
+
+ it 'executes webhooks' do
+ expect(service).to receive(:execute_hooks).once
+ .with(instance_of(WikiPage), 'create')
+
+ service.execute
end
end
end
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
index a4b9a390fe2..920be4d4c8a 100644
--- a/spec/services/wiki_pages/destroy_service_spec.rb
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -3,19 +3,20 @@ require 'spec_helper'
describe WikiPages::DestroyService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
- let(:wiki_page) { create(:wiki_page) }
- let(:service) { described_class.new(project, user) }
+ let(:page) { create(:wiki_page) }
- describe '#execute' do
- before do
- allow(service).to receive(:execute_hooks)
- project.add_master(user)
- end
+ subject(:service) { described_class.new(project, user) }
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
it 'executes webhooks' do
- service.execute(wiki_page)
+ expect(service).to receive(:execute_hooks).once
+ .with(instance_of(WikiPage), 'delete')
- expect(service).to have_received(:execute_hooks).once.with(wiki_page, 'delete')
+ service.execute(page)
end
end
end
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
index 2bccca764d7..5e36ea4cf94 100644
--- a/spec/services/wiki_pages/update_service_spec.rb
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -3,7 +3,8 @@ require 'spec_helper'
describe WikiPages::UpdateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
- let(:wiki_page) { create(:wiki_page) }
+ let(:page) { create(:wiki_page) }
+
let(:opts) do
{
content: 'New content for wiki page',
@@ -11,27 +12,28 @@ describe WikiPages::UpdateService, services: true do
message: 'New wiki message'
}
end
- let(:service) { described_class.new(project, user, opts) }
+
+ subject(:service) { described_class.new(project, user, opts) }
+
+ before do
+ project.add_developer(user)
+ end
describe '#execute' do
- context "valid params" do
- before do
- allow(service).to receive(:execute_hooks)
- project.add_master(user)
- end
-
- subject { service.execute(wiki_page) }
-
- it 'updates the wiki page' do
- is_expected.to be_valid
- expect(subject.content).to eq(opts[:content])
- expect(subject.format).to eq(opts[:format].to_sym)
- expect(subject.message).to eq(opts[:message])
- end
-
- it 'executes webhooks' do
- expect(service).to have_received(:execute_hooks).once.with(subject, 'update')
- end
+ it 'updates the wiki page' do
+ updated_page = service.execute(page)
+
+ expect(updated_page).to be_valid
+ expect(updated_page.message).to eq(opts[:message])
+ expect(updated_page.content).to eq(opts[:content])
+ expect(updated_page.format).to eq(opts[:format].to_sym)
+ end
+
+ it 'executes webhooks' do
+ expect(service).to receive(:execute_hooks).once
+ .with(instance_of(WikiPage), 'update')
+
+ service.execute(page)
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 4c2eba8fa46..994c7dcbb46 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -26,6 +26,9 @@ if ENV['CI'] && !ENV['NO_KNAPSACK']
Knapsack::Adapters::RSpecAdapter.bind
end
+# require rainbow gem String monkeypatch, so we can test SystemChecks
+require 'rainbow/ext/string'
+
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
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/matchers/execute_check.rb b/spec/support/matchers/execute_check.rb
new file mode 100644
index 00000000000..7232fad52fb
--- /dev/null
+++ b/spec/support/matchers/execute_check.rb
@@ -0,0 +1,23 @@
+RSpec::Matchers.define :execute_check do |expected|
+ match do |actual|
+ expect(actual).to eq(SystemCheck)
+ expect(actual).to receive(:run) do |*args|
+ expect(args[1]).to include(expected)
+ end
+ end
+
+ match_when_negated do |actual|
+ expect(actual).to eq(SystemCheck)
+ expect(actual).to receive(:run) do |*args|
+ expect(args[1]).not_to include(expected)
+ end
+ end
+
+ failure_message do |actual|
+ 'This matcher must be used with SystemCheck' unless actual == SystemCheck
+ end
+
+ failure_message_when_negated do |actual|
+ 'This matcher must be used with SystemCheck' unless actual == SystemCheck
+ end
+end
diff --git a/spec/support/rake_helpers.rb b/spec/support/rake_helpers.rb
index 4a8158ed79b..5cb415111d2 100644
--- a/spec/support/rake_helpers.rb
+++ b/spec/support/rake_helpers.rb
@@ -7,4 +7,9 @@ module RakeHelpers
def stub_warn_user_is_not_gitlab
allow_any_instance_of(Object).to receive(:warn_user_is_not_gitlab)
end
+
+ def silence_output
+ allow($stdout).to receive(:puts)
+ allow($stdout).to receive(:print)
+ 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/uploaders/artifact_uploader_spec.rb b/spec/uploaders/artifact_uploader_spec.rb
new file mode 100644
index 00000000000..24e2e3a9f0e
--- /dev/null
+++ b/spec/uploaders/artifact_uploader_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+describe ArtifactUploader do
+ let(:job) { create(:ci_build) }
+ let(:uploader) { described_class.new(job, :artifacts_file) }
+ let(:path) { Gitlab.config.artifacts.path }
+
+ describe '.local_artifacts_store' do
+ subject { described_class.local_artifacts_store }
+
+ it "delegate to artifacts path" do
+ expect(Gitlab.config.artifacts).to receive(:path)
+
+ subject
+ end
+ end
+
+ describe '.artifacts_upload_path' do
+ subject { described_class.artifacts_upload_path }
+
+ it { is_expected.to start_with(path) }
+ it { is_expected.to end_with('tmp/uploads/') }
+ end
+
+ describe '#store_dir' do
+ subject { uploader.store_dir }
+
+ it { is_expected.to start_with(path) }
+ it { is_expected.to end_with("#{job.project_id}/#{job.id}") }
+ end
+
+ describe '#cache_dir' do
+ subject { uploader.cache_dir }
+
+ it { is_expected.to start_with(path) }
+ it { is_expected.to end_with('tmp/cache') }
+ end
+end
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
new file mode 100644
index 00000000000..78e9d9cf46c
--- /dev/null
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -0,0 +1,56 @@
+require 'rails_helper'
+require 'carrierwave/storage/fog'
+
+describe GitlabUploader do
+ let(:uploader_class) { Class.new(described_class) }
+
+ subject { uploader_class.new }
+
+ describe '#file_storage?' do
+ context 'when file storage is used' do
+ before do
+ uploader_class.storage(:file)
+ end
+
+ it { is_expected.to be_file_storage }
+ end
+
+ context 'when is remote storage' do
+ before do
+ uploader_class.storage(:fog)
+ end
+
+ it { is_expected.not_to be_file_storage }
+ end
+ end
+
+ describe '#file_cache_storage?' do
+ context 'when file storage is used' do
+ before do
+ uploader_class.cache_storage(:file)
+ end
+
+ it { is_expected.to be_file_cache_storage }
+ end
+
+ context 'when is remote storage' do
+ before do
+ uploader_class.cache_storage(:fog)
+ end
+
+ it { is_expected.not_to be_file_cache_storage }
+ end
+ end
+
+ describe '#move_to_cache' do
+ it 'is true' do
+ expect(subject.move_to_cache).to eq(true)
+ end
+ end
+
+ describe '#move_to_store' do
+ it 'is true' do
+ expect(subject.move_to_store).to eq(true)
+ end
+ end
+end
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