summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorAchilleas Pipinellis <axil@gitlab.com>2018-11-29 20:00:56 +0100
committerAchilleas Pipinellis <axil@gitlab.com>2018-11-29 20:00:56 +0100
commit57ca2340edc4664729f3030f1a96081041493f4f (patch)
tree706f70e66aeefef1bd946dea454dc014a8701997 /spec
parent0341cfd63efcfee7422f67dd3a83df1cf392740c (diff)
parentb05a36430404aa8eaeeed8560ed03c18117f6d72 (diff)
downloadgitlab-ce-57ca2340edc4664729f3030f1a96081041493f4f.tar.gz
Merge branch 'master' into tatkins-installation-method-docs
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/users_controller_spec.rb12
-rw-r--r--spec/controllers/application_controller_spec.rb151
-rw-r--r--spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb10
-rw-r--r--spec/controllers/concerns/lfs_request_spec.rb2
-rw-r--r--spec/controllers/dashboard/projects_controller_spec.rb5
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb10
-rw-r--r--spec/controllers/dashboard_controller_spec.rb31
-rw-r--r--spec/controllers/graphql_controller_spec.rb47
-rw-r--r--spec/controllers/groups_controller_spec.rb20
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb2
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb17
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb7
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb2
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb138
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb9
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb9
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb36
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb33
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb103
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb22
-rw-r--r--spec/controllers/projects_controller_spec.rb22
-rw-r--r--spec/controllers/users_controller_spec.rb8
-rw-r--r--spec/factories/ci/builds.rb2
-rw-r--r--spec/factories/clusters/applications/helm.rb6
-rw-r--r--spec/factories/import_state.rb7
-rw-r--r--spec/factories/pool_repositories.rb5
-rw-r--r--spec/factories/projects.rb9
-rw-r--r--spec/factories/shards.rb5
-rw-r--r--spec/factories/site_statistics.rb6
-rw-r--r--spec/features/admin/admin_users_spec.rb101
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb1
-rw-r--r--spec/features/explore/new_menu_spec.rb167
-rw-r--r--spec/features/help_pages_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb54
-rw-r--r--spec/features/issues/user_comments_on_issue_spec.rb12
-rw-r--r--spec/features/issues/user_sees_breadcrumb_links_spec.rb8
-rw-r--r--spec/features/issues_spec.rb27
-rw-r--r--spec/features/markdown/mermaid_spec.rb2
-rw-r--r--spec/features/merge_request/user_assigns_themselves_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb17
-rw-r--r--spec/features/merge_request/user_sees_breadcrumb_links_spec.rb8
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb15
-rw-r--r--spec/features/merge_request/user_sees_discussions_spec.rb21
-rw-r--r--spec/features/merge_request/user_sees_empty_state_spec.rb12
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb2
-rw-r--r--spec/features/milestones/user_promotes_milestone_spec.rb32
-rw-r--r--spec/features/milestones/user_sees_breadcrumb_links_spec.rb19
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb23
-rw-r--r--spec/features/projects/environments/environment_spec.rb10
-rw-r--r--spec/features/projects/jobs_spec.rb12
-rw-r--r--spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb17
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb6
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb18
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb298
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb224
-rw-r--r--spec/features/signed_commits_spec.rb94
-rw-r--r--spec/finders/issues_finder_spec.rb36
-rw-r--r--spec/finders/pipeline_schedules_finder_spec.rb2
-rw-r--r--spec/fixtures/emails/paragraphs.eml2
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb40
-rw-r--r--spec/graphql/types/issue_type_spec.rb7
-rw-r--r--spec/graphql/types/permission_types/issue_spec.rb12
-rw-r--r--spec/graphql/types/project_type_spec.rb4
-rw-r--r--spec/helpers/tree_helper_spec.rb28
-rw-r--r--spec/initializers/attr_encrypted_no_db_connection_spec.rb30
-rw-r--r--spec/initializers/secret_token_spec.rb2
-rw-r--r--spec/javascripts/api_spec.js12
-rw-r--r--spec/javascripts/blob_edit/blob_bundle_spec.js30
-rw-r--r--spec/javascripts/clusters/components/applications_spec.js12
-rw-r--r--spec/javascripts/clusters/services/mock_data.js11
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js8
-rw-r--r--spec/javascripts/diffs/components/app_spec.js40
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js21
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js6
-rw-r--r--spec/javascripts/diffs/components/diff_line_note_form_spec.js1
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js8
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js71
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js61
-rw-r--r--spec/javascripts/helpers/scroll_into_view_promise.js28
-rw-r--r--spec/javascripts/helpers/wait_for_attribute_change.js16
-rw-r--r--spec/javascripts/ide/components/ide_spec.js2
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js4
-rw-r--r--spec/javascripts/ide/stores/actions/merge_request_spec.js6
-rw-r--r--spec/javascripts/ide/stores/actions/tree_spec.js2
-rw-r--r--spec/javascripts/ide/stores/modules/branches/actions_spec.js2
-rw-r--r--spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js58
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/actions_spec.js6
-rw-r--r--spec/javascripts/issuable_suggestions/components/app_spec.js96
-rw-r--r--spec/javascripts/issuable_suggestions/components/item_spec.js139
-rw-r--r--spec/javascripts/issuable_suggestions/mock_data.js26
-rw-r--r--spec/javascripts/issue_show/components/edited_spec.js10
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js12
-rw-r--r--spec/javascripts/lazy_loader_spec.js174
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js34
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js34
-rw-r--r--spec/javascripts/notes/components/diff_with_note_spec.js3
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js55
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js60
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js26
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js47
-rw-r--r--spec/javascripts/performance_bar/components/detailed_metric_spec.js2
-rw-r--r--spec/javascripts/shared/popover_spec.js8
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js28
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js7
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js13
-rw-r--r--spec/lib/banzai/filter/absolute_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb12
-rw-r--r--spec/lib/extracts_path_spec.rb26
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb18
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb94
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb85
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_columns_spec.rb25
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb94
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/chunked_io_spec.rb63
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb4
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/check_info_spec.rb4
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb2
-rw-r--r--spec/lib/gitlab/database_spec.rb22
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb14
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/email/reply_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers_spec.rb8
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb12
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb200
-rw-r--r--spec/lib/gitlab/git/merge_base_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb13
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb31
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb14
-rw-r--r--spec/lib/gitlab/git_ref_validator_spec.rb1
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb126
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/parallel_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb28
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb29
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb8
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/multi_collection_paginator_spec.rb2
-rw-r--r--spec/lib/gitlab/profiler_spec.rb42
-rw-r--r--spec/lib/gitlab/prometheus/query_variables_spec.rb33
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb120
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb3
-rw-r--r--spec/mailers/notify_spec.rb2
-rw-r--r--spec/migrations/backfill_store_project_full_path_in_repo_spec.rb98
-rw-r--r--spec/migrations/cleanup_environments_external_url_spec.rb28
-rw-r--r--spec/migrations/migrate_forbidden_redirect_uris_spec.rb48
-rw-r--r--spec/migrations/migrate_issues_to_ghost_user_spec.rb12
-rw-r--r--spec/models/blob_spec.rb21
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb126
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb79
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb2
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb2
-rw-r--r--spec/models/clusters/applications/knative_spec.rb47
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb2
-rw-r--r--spec/models/clusters/applications/runner_spec.rb10
-rw-r--r--spec/models/clusters/cluster_spec.rb3
-rw-r--r--spec/models/concerns/relative_positioning_spec.rb8
-rw-r--r--spec/models/list_spec.rb4
-rw-r--r--spec/models/merge_request_spec.rb62
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/pool_repository_spec.rb26
-rw-r--r--spec/models/project_import_state_spec.rb112
-rw-r--r--spec/models/project_repository_spec.rb23
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb17
-rw-r--r--spec/models/project_spec.rb348
-rw-r--r--spec/models/project_wiki_spec.rb250
-rw-r--r--spec/models/remote_mirror_spec.rb8
-rw-r--r--spec/models/repository_spec.rb412
-rw-r--r--spec/models/site_statistic_spec.rb81
-rw-r--r--spec/models/user_spec.rb36
-rw-r--r--spec/models/wiki_page_spec.rb280
-rw-r--r--spec/policies/ci/pipeline_schedule_policy_spec.rb4
-rw-r--r--spec/policies/note_policy_spec.rb79
-rw-r--r--spec/policies/project_policy_spec.rb2
-rw-r--r--spec/requests/api/applications_spec.rb12
-rw-r--r--spec/requests/api/files_spec.rb8
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb59
-rw-r--r--spec/requests/api/groups_spec.rb28
-rw-r--r--spec/requests/api/helpers_spec.rb13
-rw-r--r--spec/requests/api/issues_spec.rb115
-rw-r--r--spec/requests/api/merge_requests_spec.rb30
-rw-r--r--spec/requests/api/project_import_spec.rb10
-rw-r--r--spec/requests/api/repositories_spec.rb6
-rw-r--r--spec/requests/api/runner_spec.rb24
-rw-r--r--spec/requests/api/snippets_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/add_reference_spec.rb12
-rw-r--r--spec/rubocop/cop/safe_params_spec.rb39
-rw-r--r--spec/services/ci/archive_trace_service_spec.rb51
-rw-r--r--spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb38
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb28
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb70
-rw-r--r--spec/services/files/multi_service_spec.rb39
-rw-r--r--spec/services/merge_requests/build_service_spec.rb34
-rw-r--r--spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb2
-rw-r--r--spec/services/projects/create_from_template_service_spec.rb2
-rw-r--r--spec/services/users/set_status_service_spec.rb2
-rw-r--r--spec/support/controllers/sessionless_auth_controller_shared_examples.rb92
-rw-r--r--spec/support/gitaly.rb16
-rw-r--r--spec/support/helpers/gpg_helpers.rb6
-rw-r--r--spec/support/helpers/prometheus_helpers.rb4
-rw-r--r--spec/support/import_export/export_file_helper.rb2
-rw-r--r--spec/support/shared_contexts/url_shared_context.rb17
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/requests/api/merge_requests_list.rb17
-rw-r--r--spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb33
-rw-r--r--spec/tasks/cache/clear/redis_spec.rb5
-rw-r--r--spec/tasks/gitlab/site_statistics_rake_spec.rb23
-rw-r--r--spec/validators/url_validator_spec.rb26
-rw-r--r--spec/views/layouts/header/_new_dropdown.haml_spec.rb134
-rw-r--r--spec/workers/archive_trace_worker_spec.rb8
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb10
-rw-r--r--spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb6
-rw-r--r--spec/workers/concerns/project_import_options_spec.rb14
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb6
-rw-r--r--spec/workers/gitlab/github_import/advance_stage_worker_spec.rb30
-rw-r--r--spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb22
-rw-r--r--spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb3
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb5
-rw-r--r--spec/workers/repository_import_worker_spec.rb29
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb2
230 files changed, 5043 insertions, 2817 deletions
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index f350641a643..3dd0b2623ac 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -264,5 +264,17 @@ describe Admin::UsersController do
expect(flash[:alert]).to eq("You are now impersonating #{user.username}")
end
end
+
+ context "when impersonation is disabled" do
+ before do
+ stub_config_setting(impersonation_enabled: false)
+ end
+
+ it "shows error page" do
+ post :impersonate, id: user.username
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index efc3ce74627..1b585bcd4c6 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -107,59 +107,6 @@ describe ApplicationController do
end
end
- describe "#authenticate_user_from_personal_access_token!" do
- before do
- stub_authentication_activity_metrics(debug: false)
- end
-
- controller(described_class) do
- def index
- render text: 'authenticated'
- end
- end
-
- let(:personal_access_token) { create(:personal_access_token, user: user) }
-
- context "when the 'personal_access_token' param is populated with the personal access token" do
- it "logs the user in" do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- get :index, private_token: personal_access_token.token
-
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq('authenticated')
- end
- end
-
- context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
- it "logs the user in" do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- @request.headers["PRIVATE-TOKEN"] = personal_access_token.token
- get :index
-
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq('authenticated')
- end
- end
-
- it "doesn't log the user in otherwise" do
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
-
- get :index, private_token: "token"
-
- expect(response.status).not_to eq(200)
- expect(response.body).not_to eq('authenticated')
- end
- end
-
describe 'session expiration' do
controller(described_class) do
# The anonymous controller will report 401 and fail to run any actions.
@@ -224,74 +171,6 @@ describe ApplicationController do
end
end
- describe '#authenticate_sessionless_user!' do
- before do
- stub_authentication_activity_metrics(debug: false)
- end
-
- describe 'authenticating a user from a feed token' do
- controller(described_class) do
- def index
- render text: 'authenticated'
- end
- end
-
- context "when the 'feed_token' param is populated with the feed token" do
- context 'when the request format is atom' do
- it "logs the user in" do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- get :index, feed_token: user.feed_token, format: :atom
-
- expect(response).to have_gitlab_http_status 200
- expect(response.body).to eq 'authenticated'
- end
- end
-
- context 'when the request format is ics' do
- it "logs the user in" do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- get :index, feed_token: user.feed_token, format: :ics
-
- expect(response).to have_gitlab_http_status 200
- expect(response.body).to eq 'authenticated'
- end
- end
-
- context 'when the request format is neither atom nor ics' do
- it "doesn't log the user in" do
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
-
- get :index, feed_token: user.feed_token
-
- expect(response.status).not_to have_gitlab_http_status 200
- expect(response.body).not_to eq 'authenticated'
- end
- end
- end
-
- context "when the 'feed_token' param is populated with an invalid feed token" do
- it "doesn't log the user" do
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
-
- get :index, feed_token: 'token', format: :atom
-
- expect(response.status).not_to eq 200
- expect(response.body).not_to eq 'authenticated'
- end
- end
- end
- end
-
describe '#route_not_found' do
it 'renders 404 if authenticated' do
allow(controller).to receive(:current_user).and_return(user)
@@ -557,36 +436,6 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(200)
end
-
- context 'for sessionless users' do
- render_views
-
- before do
- sign_out user
- end
-
- it 'renders a 403 when the sessionless user did not accept the terms' do
- get :index, feed_token: user.feed_token, format: :atom
-
- expect(response).to have_gitlab_http_status(403)
- end
-
- it 'renders the error message when the format was html' do
- get :index,
- private_token: create(:personal_access_token, user: user).token,
- format: :html
-
- expect(response.body).to have_content /accept the terms of service/i
- end
-
- it 'renders a 200 when the sessionless user accepted the terms' do
- accept_terms(user)
-
- get :index, feed_token: user.feed_token, format: :atom
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
end
end
diff --git a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
index d20471ef603..3c9452cc42a 100644
--- a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
+++ b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
@@ -27,11 +27,11 @@ describe ControllerWithCrossProjectAccessCheck do
if: -> { if_condition }
def index
- render nothing: true
+ head :ok
end
def show
- render nothing: true
+ head :ok
end
def unless_condition
@@ -88,15 +88,15 @@ describe ControllerWithCrossProjectAccessCheck do
if: -> { if_condition }
def index
- render nothing: true
+ head :ok
end
def show
- render nothing: true
+ head :ok
end
def edit
- render nothing: true
+ head :ok
end
def unless_condition
diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb
index 33b23db302a..76c878ec5d7 100644
--- a/spec/controllers/concerns/lfs_request_spec.rb
+++ b/spec/controllers/concerns/lfs_request_spec.rb
@@ -10,7 +10,7 @@ describe LfsRequest do
def show
storage_project
- render nothing: true
+ head :ok
end
def project
diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb
new file mode 100644
index 00000000000..2975205e09c
--- /dev/null
+++ b/spec/controllers/dashboard/projects_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Dashboard::ProjectsController do
+ it_behaves_like 'authenticates sessionless user', :index, :atom
+end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index b4a731fd3a3..e2c799f5205 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -42,6 +42,16 @@ describe Dashboard::TodosController do
end
end
+ context 'group authorization' do
+ it 'renders 404 when user does not have read access on given group' do
+ unauthorized_group = create(:group, :private)
+
+ get :index, group_id: unauthorized_group.id
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
context 'when using pagination' do
let(:last_page) { user.todos.page.total_pages }
let!(:issues) { create_list(:issue, 3, project: project, assignees: [user]) }
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index 187542ba30c..c857a78d5e8 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -1,21 +1,26 @@
require 'spec_helper'
describe DashboardController do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
+ context 'signed in' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
- describe 'GET issues' do
- it_behaves_like 'issuables list meta-data', :issue, :issues
- it_behaves_like 'issuables requiring filter', :issues
- end
+ describe 'GET issues' do
+ it_behaves_like 'issuables list meta-data', :issue, :issues
+ it_behaves_like 'issuables requiring filter', :issues
+ end
- describe 'GET merge requests' do
- it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
- it_behaves_like 'issuables requiring filter', :merge_requests
+ describe 'GET merge requests' do
+ it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
+ it_behaves_like 'issuables requiring filter', :merge_requests
+ end
end
+
+ it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first
+ it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics
end
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index 1449036e148..949ad532365 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -52,15 +52,58 @@ describe GraphqlController do
end
end
+ context 'token authentication' do
+ before do
+ stub_authentication_activity_metrics(debug: false)
+ end
+
+ let(:user) { create(:user, username: 'Simon') }
+ let(:personal_access_token) { create(:personal_access_token, user: user) }
+
+ context "when the 'personal_access_token' param is populated with the personal access token" do
+ it 'logs the user in' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
+
+ run_test_query!(private_token: personal_access_token.token)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(query_response).to eq('echo' => '"Simon" says: test success')
+ end
+ end
+
+ context 'when the personal access token has no api scope' do
+ it 'does not log the user in' do
+ personal_access_token.update(scopes: [:read_user])
+
+ run_test_query!(private_token: personal_access_token.token)
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(query_response).to eq('echo' => 'nil says: test success')
+ end
+ end
+
+ context 'without token' do
+ it 'shows public data' do
+ run_test_query!
+
+ expect(query_response).to eq('echo' => 'nil says: test success')
+ end
+ end
+ end
+
# Chosen to exercise all the moving parts in GraphqlController#execute
- def run_test_query!(variables: { 'text' => 'test success' })
+ def run_test_query!(variables: { 'text' => 'test success' }, private_token: nil)
query = <<~QUERY
query Echo($text: String) {
echo(text: $text)
}
QUERY
- post :execute, query: query, operationName: 'Echo', variables: variables
+ post :execute, query: query, operationName: 'Echo', variables: variables, private_token: private_token
end
def query_response
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 4de61b65f71..f6c85102830 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -606,4 +606,24 @@ describe GroupsController do
end
end
end
+
+ context 'token authentication' do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
+ before do
+ default_params.merge!(id: group)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user', :issues, :atom, public: true do
+ before do
+ default_params.merge!(id: group, author_id: user.id)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics, public: true do
+ before do
+ default_params.merge!(id: group)
+ end
+ end
+ end
end
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index 77060fdc3be..db912641894 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -126,7 +126,7 @@ describe Import::BitbucketServerController do
end
it 'assigns repository categories' do
- created_project = create(:project, import_type: 'bitbucket_server', creator_id: user.id, import_status: 'finished', import_source: @created_repo.browse_url)
+ created_project = create(:project, :import_finished, import_type: 'bitbucket_server', creator_id: user.id, import_source: @created_repo.browse_url)
repos = instance_double(BitbucketServer::Collection)
expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index ace8a954e92..b4219856fc0 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -40,6 +40,23 @@ describe Oauth::ApplicationsController do
expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(profile_path)
end
+
+ context 'redirect_uri' do
+ render_views
+
+ it 'shows an error for a forbidden URI' do
+ invalid_uri_params = {
+ doorkeeper_application: {
+ name: 'foo',
+ redirect_uri: 'javascript://alert()'
+ }
+ }
+
+ post :create, invalid_uri_params
+
+ expect(response.body).to include 'Redirect URI is forbidden by the server'
+ end
+ end
end
end
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index ea26bc83353..685db8488f0 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -62,8 +62,15 @@ describe Profiles::KeysController do
it "responds with text/plain content type" do
get :get_keys, username: user.username
+
expect(response.content_type).to eq("text/plain")
end
+
+ it "responds with attachment content disposition" do
+ get :get_keys, username: user.username
+
+ expect(response.headers['Content-Disposition']).to eq('attachment')
+ end
end
end
end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 5fdf7f1229d..3c5a21c47fa 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -152,7 +152,7 @@ describe Projects::BlobController do
expect(match_line['meta_data']).to have_key('new_pos')
end
- it 'does not add top match line when when "since" is equal 1' do
+ it 'does not add top match line when "since" is equal 1' do
do_get(since: 1, to: 10, offset: 10, from_merge_request: true)
match_line = JSON.parse(response.body).first
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index a43bdd3ea80..5c72dab698c 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -5,87 +5,115 @@ describe Projects::CommitsController do
let(:user) { create(:user) }
before do
- sign_in(user)
project.add_maintainer(user)
end
- describe "GET commits_root" do
- context "no ref is provided" do
- it 'should redirect to the default branch of the project' do
- get(:commits_root,
- namespace_id: project.namespace,
- project_id: project)
+ context 'signed in' do
+ before do
+ sign_in(user)
+ end
+
+ describe "GET commits_root" do
+ context "no ref is provided" do
+ it 'should redirect to the default branch of the project' do
+ get(:commits_root,
+ namespace_id: project.namespace,
+ project_id: project)
- expect(response).to redirect_to project_commits_path(project)
+ expect(response).to redirect_to project_commits_path(project)
+ end
end
end
- end
- describe "GET show" do
- render_views
+ describe "GET show" do
+ render_views
- context 'with file path' do
- before do
- get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: id)
- end
+ context 'with file path' do
+ before do
+ get(:show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id)
+ end
- context "valid branch, valid file" do
- let(:id) { 'master/README.md' }
+ context "valid branch, valid file" do
+ let(:id) { 'master/README.md' }
- it { is_expected.to respond_with(:success) }
- end
+ it { is_expected.to respond_with(:success) }
+ end
- context "valid branch, invalid file" do
- let(:id) { 'master/invalid-path.rb' }
+ context "valid branch, invalid file" do
+ let(:id) { 'master/invalid-path.rb' }
- it { is_expected.to respond_with(:not_found) }
- end
+ it { is_expected.to respond_with(:not_found) }
+ end
- context "invalid branch, valid file" do
- let(:id) { 'invalid-branch/README.md' }
+ context "invalid branch, valid file" do
+ let(:id) { 'invalid-branch/README.md' }
- it { is_expected.to respond_with(:not_found) }
+ it { is_expected.to respond_with(:not_found) }
+ end
end
- end
- context "when the ref name ends in .atom" do
- context "when the ref does not exist with the suffix" do
- before do
- get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: "master.atom")
+ context "when the ref name ends in .atom" do
+ context "when the ref does not exist with the suffix" do
+ before do
+ get(:show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: "master.atom")
+ end
+
+ it "renders as atom" do
+ expect(response).to be_success
+ expect(response.content_type).to eq('application/atom+xml')
+ end
+
+ it 'renders summary with type=html' do
+ expect(response.body).to include('<summary type="html">')
+ end
end
- it "renders as atom" do
- expect(response).to be_success
- expect(response.content_type).to eq('application/atom+xml')
- end
+ context "when the ref exists with the suffix" do
+ before do
+ commit = project.repository.commit('master')
- it 'renders summary with type=html' do
- expect(response.body).to include('<summary type="html">')
+ allow_any_instance_of(Repository).to receive(:commit).and_call_original
+ allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit)
+
+ get(:show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: "master.atom")
+ end
+
+ it "renders as HTML" do
+ expect(response).to be_success
+ expect(response.content_type).to eq('text/html')
+ end
end
end
+ end
+ end
- context "when the ref exists with the suffix" do
+ context 'token authentication' do
+ context 'public project' do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
- commit = project.repository.commit('master')
+ public_project = create(:project, :repository, :public)
- allow_any_instance_of(Repository).to receive(:commit).and_call_original
- allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit)
-
- get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: "master.atom")
+ default_params.merge!(namespace_id: public_project.namespace, project_id: public_project, id: "master.atom")
end
+ end
+ end
+
+ context 'private project' do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: false do
+ before do
+ private_project = create(:project, :repository, :private)
+ private_project.add_maintainer(user)
- it "renders as HTML" do
- expect(response).to be_success
- expect(response.content_type).to eq('text/html')
+ default_params.merge!(namespace_id: private_project.namespace, project_id: private_project, id: "master.atom")
end
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index bc17331f531..5fa0488014f 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -217,7 +217,10 @@ describe Projects::EnvironmentsController do
end
it 'loads the terminals for the environment' do
- expect_any_instance_of(Environment).to receive(:terminals)
+ # In EE we have to stub EE::Environment since it overwrites the
+ # "terminals" method.
+ expect_any_instance_of(defined?(EE) ? EE::Environment : Environment)
+ .to receive(:terminals)
get :terminal, environment_params
end
@@ -240,7 +243,9 @@ describe Projects::EnvironmentsController do
context 'and valid id' do
it 'returns the first terminal for the environment' do
- expect_any_instance_of(Environment)
+ # In EE we have to stub EE::Environment since it overwrites the
+ # "terminals" method.
+ expect_any_instance_of(defined?(EE) ? EE::Environment : Environment)
.to receive(:terminals)
.and_return([:fake_terminal])
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index adf3c78ae51..cdc63f5aab3 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -26,10 +26,11 @@ describe Projects::ImportsController do
context 'when repository exists' do
let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git') }
+ let(:import_state) { project.import_state }
context 'when import is in progress' do
before do
- project.update(import_status: :started)
+ import_state.update(status: :started)
end
it 'renders template' do
@@ -47,7 +48,7 @@ describe Projects::ImportsController do
context 'when import failed' do
before do
- project.update(import_status: :failed)
+ import_state.update(status: :failed)
end
it 'redirects to new_namespace_project_import_path' do
@@ -59,7 +60,7 @@ describe Projects::ImportsController do
context 'when import finished' do
before do
- project.update(import_status: :finished)
+ import_state.update(status: :finished)
end
context 'when project is a fork' do
@@ -108,7 +109,7 @@ describe Projects::ImportsController do
context 'when import never happened' do
before do
- project.update(import_status: :none)
+ import_state.update(status: :none)
end
it 'redirects to namespace_project_path' do
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 80138183c07..02930edbf72 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1068,4 +1068,40 @@ describe Projects::IssuesController do
end
end
end
+
+ context 'private project with token authentication' do
+ let(:private_project) { create(:project, :private) }
+
+ it_behaves_like 'authenticates sessionless user', :index, :atom do
+ before do
+ default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
+
+ private_project.add_maintainer(user)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user', :calendar, :ics do
+ before do
+ default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
+
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+
+ context 'public project with token authentication' do
+ let(:public_project) { create(:project, :public) }
+
+ it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
+ before do
+ default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user', :calendar, :ics, public: true do
+ before do
+ default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index ccd4fc4db3a..658aa2a6738 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -143,11 +143,27 @@ describe Projects::MilestonesController do
end
describe '#promote' do
+ let(:group) { create(:group) }
+
+ before do
+ project.update(namespace: group)
+ end
+
+ context 'when user does not have permission to promote milestone' do
+ before do
+ group.add_guest(user)
+ end
+
+ it 'renders 404' do
+ post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
context 'promotion succeeds' do
before do
- group = create(:group)
group.add_developer(user)
- milestone.project.update(namespace: group)
end
it 'shows group milestone' do
@@ -166,12 +182,17 @@ describe Projects::MilestonesController do
end
end
- context 'promotion fails' do
- it 'shows project milestone' do
+ context 'when user cannot admin group milestones' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'renders 404' do
+ project.update(namespace: user.namespace)
+
post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
- expect(response).to redirect_to(project_milestone_path(project, milestone))
- expect(flash[:alert]).to eq('Promotion failed - Project does not belong to a group.')
+ expect(response).to have_gitlab_http_status(404)
end
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 9ac7b8ee8a8..d2a26068362 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -283,14 +283,14 @@ describe Projects::NotesController do
def post_create(extra_params = {})
post :create, {
- note: { note: 'some other note' },
- namespace_id: project.namespace,
- project_id: project,
- target_type: 'merge_request',
- target_id: merge_request.id,
- note_project_id: forked_project.id,
- in_reply_to_discussion_id: existing_comment.discussion_id
- }.merge(extra_params)
+ note: { note: 'some other note', noteable_id: merge_request.id },
+ namespace_id: project.namespace,
+ project_id: project,
+ target_type: 'merge_request',
+ target_id: merge_request.id,
+ note_project_id: forked_project.id,
+ in_reply_to_discussion_id: existing_comment.discussion_id
+ }.merge(extra_params)
end
context 'when the note_project_id is not correct' do
@@ -324,6 +324,30 @@ describe Projects::NotesController do
end
end
+ context 'when target_id and noteable_id do not match' do
+ let(:locked_issue) { create(:issue, :locked, project: project) }
+ let(:issue) {create(:issue, project: project)}
+
+ before do
+ project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
+ project.project_member(user).destroy
+ end
+
+ it 'uses target_id and ignores noteable_id' do
+ request_params = {
+ note: { note: 'some note', noteable_type: 'Issue', noteable_id: locked_issue.id },
+ target_type: 'issue',
+ target_id: issue.id,
+ project_id: project,
+ namespace_id: project.namespace
+ }
+
+ expect { post :create, request_params }.to change { issue.notes.count }.by(1)
+ .and change { locked_issue.notes.count }.by(0)
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
context 'when the merge request discussion is locked' do
before do
project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
@@ -376,35 +400,60 @@ describe Projects::NotesController do
end
describe 'PUT update' do
- let(:request_params) do
- {
- namespace_id: project.namespace,
- project_id: project,
- id: note,
- format: :json,
- note: {
- note: "New comment"
+ context "should update the note with a valid issue" do
+ let(:request_params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: note,
+ format: :json,
+ note: {
+ note: "New comment"
+ }
}
- }
- end
+ end
- before do
- sign_in(note.author)
- project.add_developer(note.author)
+ before do
+ sign_in(note.author)
+ project.add_developer(note.author)
+ end
+
+ it "updates the note" do
+ expect { put :update, request_params }.to change { note.reload.note }
+ end
end
+ context "doesnt update the note" do
+ let(:issue) { create(:issue, :confidential, project: project) }
+ let(:note) { create(:note, noteable: issue, project: project) }
- it "updates the note" do
- expect { put :update, request_params }.to change { note.reload.note }
+ before do
+ sign_in(user)
+ project.add_guest(user)
+ end
+
+ it "disallows edits when the issue is confidential and the user has guest permissions" do
+ request_params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: note,
+ format: :json,
+ note: {
+ note: "New comment"
+ }
+ }
+ expect { put :update, request_params }.not_to change { note.reload.note }
+ expect(response).to have_gitlab_http_status(404)
+ end
end
end
describe 'DELETE destroy' do
let(:request_params) do
{
- namespace_id: project.namespace,
- project_id: project,
- id: note,
- format: :js
+ namespace_id: project.namespace,
+ project_id: project,
+ id: note,
+ format: :js
}
end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index c48f41ca12e..6fbf75d0259 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -35,4 +35,26 @@ describe Projects::TagsController do
it { is_expected.to respond_with(:not_found) }
end
end
+
+ context 'private project with token authentication' do
+ let(:private_project) { create(:project, :repository, :private) }
+
+ it_behaves_like 'authenticates sessionless user', :index, :atom do
+ before do
+ default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
+
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+
+ context 'public project with token authentication' do
+ let(:public_project) { create(:project, :repository, :public) }
+
+ it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
+ before do
+ default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 3bc9cbe64c5..7849bec4762 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -882,6 +882,28 @@ describe ProjectsController do
end
end
+ context 'private project with token authentication' do
+ let(:private_project) { create(:project, :private) }
+
+ it_behaves_like 'authenticates sessionless user', :show, :atom do
+ before do
+ default_params.merge!(id: private_project, namespace_id: private_project.namespace)
+
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+
+ context 'public project with token authentication' do
+ let(:public_project) { create(:project, :public) }
+
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
+ before do
+ default_params.merge!(id: public_project, namespace_id: public_project.namespace)
+ end
+ end
+ end
+
def project_moved_message(redirect_route, project)
"Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path."
end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 071f96a729e..fe438e71e9e 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -395,6 +395,14 @@ describe UsersController do
end
end
+ context 'token authentication' do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
+ before do
+ default_params.merge!(username: user.username)
+ end
+ end
+ end
+
def user_moved_message(redirect_route, user)
"User '#{redirect_route.path}' was moved to '#{user.full_path}'. Please update any links and bookmarks that may still have the old path."
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 90754319f05..07c1fc31152 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -308,7 +308,7 @@ FactoryBot.define do
trait :with_runner_session do
after(:build) do |build|
- build.build_runner_session(url: 'ws://localhost')
+ build.build_runner_session(url: 'https://localhost')
end
end
end
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index ff65c76cf26..fe56ac5b71d 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -49,11 +49,17 @@ FactoryBot.define do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
+ factory :clusters_applications_cert_managers, class: Clusters::Applications::CertManager do
+ email 'admin@example.com'
+ cluster factory: %i(cluster with_installed_helm provided_by_gcp)
+ end
+
factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
factory :clusters_applications_runner, class: Clusters::Applications::Runner do
+ runner factory: %i(ci_runner)
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
diff --git a/spec/factories/import_state.rb b/spec/factories/import_state.rb
index 15d0a9d466a..d6de26dccbc 100644
--- a/spec/factories/import_state.rb
+++ b/spec/factories/import_state.rb
@@ -5,6 +5,7 @@ FactoryBot.define do
transient do
import_url { generate(:url) }
+ import_type nil
end
trait :repository do
@@ -32,7 +33,11 @@ FactoryBot.define do
end
after(:create) do |import_state, evaluator|
- import_state.project.update_columns(import_url: evaluator.import_url)
+ columns = {}
+ columns[:import_url] = evaluator.import_url unless evaluator.import_url.blank?
+ columns[:import_type] = evaluator.import_type unless evaluator.import_type.blank?
+
+ import_state.project.update_columns(columns)
end
end
end
diff --git a/spec/factories/pool_repositories.rb b/spec/factories/pool_repositories.rb
new file mode 100644
index 00000000000..2ed0844ed47
--- /dev/null
+++ b/spec/factories/pool_repositories.rb
@@ -0,0 +1,5 @@
+FactoryBot.define do
+ factory :pool_repository do
+ shard
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index e4823a5adf1..1906c06a211 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -30,6 +30,8 @@ FactoryBot.define do
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
group_runners_enabled nil
+ import_status nil
+ import_jid nil
end
after(:create) do |project, evaluator|
@@ -64,6 +66,13 @@ FactoryBot.define do
# assign the delegated `#ci_cd_settings` attributes after create
project.reload.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
+
+ if evaluator.import_status
+ import_state = project.import_state || project.build_import_state
+ import_state.status = evaluator.import_status
+ import_state.jid = evaluator.import_jid
+ import_state.save
+ end
end
trait :public do
diff --git a/spec/factories/shards.rb b/spec/factories/shards.rb
new file mode 100644
index 00000000000..c095fa5f0a0
--- /dev/null
+++ b/spec/factories/shards.rb
@@ -0,0 +1,5 @@
+FactoryBot.define do
+ factory :shard do
+ name "default"
+ end
+end
diff --git a/spec/factories/site_statistics.rb b/spec/factories/site_statistics.rb
deleted file mode 100644
index 2533d0eecc2..00000000000
--- a/spec/factories/site_statistics.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryBot.define do
- factory :site_statistics, class: 'SiteStatistic' do
- id 1
- repositories_count 999
- end
-end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index f7c7a257075..d5516b334b9 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -205,75 +205,118 @@ describe "Admin::Users" do
describe 'Impersonation' do
let(:another_user) { create(:user) }
- before do
- visit admin_user_path(another_user)
- end
-
context 'before impersonating' do
- it 'shows impersonate button for other users' do
- expect(page).to have_content('Impersonate')
+ subject { visit admin_user_path(user_to_visit) }
+
+ let(:user_to_visit) { another_user }
+
+ context 'for other users' do
+ it 'shows impersonate button for other users' do
+ subject
+
+ expect(page).to have_content('Impersonate')
+ end
end
- it 'does not show impersonate button for admin itself' do
- visit admin_user_path(current_user)
+ context 'for admin itself' do
+ let(:user_to_visit) { current_user }
- expect(page).not_to have_content('Impersonate')
+ it 'does not show impersonate button for admin itself' do
+ subject
+
+ expect(page).not_to have_content('Impersonate')
+ end
end
- it 'does not show impersonate button for blocked user' do
- another_user.block
+ context 'for blocked user' do
+ before do
+ another_user.block
+ end
- visit admin_user_path(another_user)
+ it 'does not show impersonate button for blocked user' do
+ subject
- expect(page).not_to have_content('Impersonate')
+ expect(page).not_to have_content('Impersonate')
+ end
+ end
+
+ context 'when impersonation is disabled' do
+ before do
+ stub_config_setting(impersonation_enabled: false)
+ end
- another_user.activate
+ it 'does not show impersonate button' do
+ subject
+
+ expect(page).not_to have_content('Impersonate')
+ end
end
end
context 'when impersonating' do
+ subject { click_link 'Impersonate' }
+
before do
- click_link 'Impersonate'
+ visit admin_user_path(another_user)
end
it 'logs in as the user when impersonate is clicked' do
+ subject
+
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username)
end
it 'sees impersonation log out icon' do
- icon = first('.fa.fa-user-secret')
+ subject
+ icon = first('.fa.fa-user-secret')
expect(icon).not_to be nil
end
- it 'logs out of impersonated user back to original user' do
- find(:css, 'li.impersonation a').click
-
- expect(page.find(:css, '.header-user .profile-link')['data-user']).to eq(current_user.username)
- end
+ context 'a user with an expired password' do
+ before do
+ another_user.update(password_expires_at: Time.now - 5.minutes)
+ end
- it 'is redirected back to the impersonated users page in the admin after stopping' do
- find(:css, 'li.impersonation a').click
+ it 'does not redirect to password change page' do
+ subject
- expect(current_path).to eq("/admin/users/#{another_user.username}")
+ expect(current_path).to eq('/')
+ end
end
end
- context 'when impersonating a user with an expired password' do
+ context 'ending impersonation' do
+ subject { find(:css, 'li.impersonation a').click }
+
before do
- another_user.update(password_expires_at: Time.now - 5.minutes)
+ visit admin_user_path(another_user)
click_link 'Impersonate'
end
- it 'does not redirect to password change page' do
- expect(current_path).to eq('/')
+ it 'logs out of impersonated user back to original user' do
+ subject
+
+ expect(page.find(:css, '.header-user .profile-link')['data-user']).to eq(current_user.username)
end
it 'is redirected back to the impersonated users page in the admin after stopping' do
- find(:css, 'li.impersonation a').click
+ subject
expect(current_path).to eq("/admin/users/#{another_user.username}")
end
+
+ context 'a user with an expired password' do
+ before do
+ another_user.update(password_expires_at: Time.now - 5.minutes)
+ end
+
+ it 'is redirected back to the impersonated users page in the admin after stopping' do
+ subject
+
+ expect(current_path).to eq("/admin/users/#{another_user.username}")
+ end
+ end
end
end
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 282bf542e77..9ffa75aee47 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -6,6 +6,7 @@ describe 'Dashboard Merge Requests' do
include ProjectForksHelper
let(:current_user) { create :user }
+ let(:user) { current_user }
let(:project) { create(:project) }
let(:public_project) { create(:project, :public, :repository) }
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
deleted file mode 100644
index 259f22139ef..00000000000
--- a/spec/features/explore/new_menu_spec.rb
+++ /dev/null
@@ -1,167 +0,0 @@
-require 'spec_helper'
-
-describe 'Top Plus Menu', :js do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
- let(:public_project) { create(:project, :public) }
-
- before do
- group.add_owner(user)
- end
-
- context 'used by full user' do
- before do
- sign_in(user)
- end
-
- it 'click on New project shows new project page' do
- visit root_dashboard_path
-
- click_topmenuitem("New project")
-
- expect(page).to have_content('Project URL')
- expect(page).to have_content('Project name')
- end
-
- it 'click on New group shows new group page' do
- visit root_dashboard_path
-
- click_topmenuitem("New group")
-
- expect(page).to have_content('Group URL')
- expect(page).to have_content('Group name')
- end
-
- it 'click on New snippet shows new snippet page' do
- visit root_dashboard_path
-
- click_topmenuitem("New snippet")
-
- expect(page).to have_content('New Snippet')
- expect(page).to have_content('Title')
- end
-
- it 'click on New issue shows new issue page' do
- visit project_path(project)
-
- click_topmenuitem("New issue")
-
- expect(page).to have_content('New Issue')
- expect(page).to have_content('Title')
- end
-
- it 'click on New merge request shows new merge request page' do
- visit project_path(project)
-
- click_topmenuitem("New merge request")
-
- expect(page).to have_content('New Merge Request')
- expect(page).to have_content('Source branch')
- expect(page).to have_content('Target branch')
- end
-
- it 'click on New project snippet shows new snippet page' do
- visit project_path(project)
-
- page.within '.header-content' do
- find('.header-new-dropdown-toggle').click
- expect(page).to have_selector('.header-new.dropdown.show', count: 1)
- find('.header-new-project-snippet a').click
- end
-
- expect(page).to have_content('New Snippet')
- expect(page).to have_content('Title')
- end
-
- it 'Click on New subgroup shows new group page', :nested_groups do
- visit group_path(group)
-
- click_topmenuitem("New subgroup")
-
- expect(page).to have_content('Group URL')
- expect(page).to have_content('Group name')
- end
-
- it 'Click on New project in group shows new project page' do
- visit group_path(group)
-
- page.within '.header-content' do
- find('.header-new-dropdown-toggle').click
- expect(page).to have_selector('.header-new.dropdown.show', count: 1)
- find('.header-new-group-project a').click
- end
-
- expect(page).to have_content('Project URL')
- expect(page).to have_content('Project name')
- end
- end
-
- context 'used by guest user' do
- let(:guest_user) { create(:user) }
-
- before do
- group.add_guest(guest_user)
- project.add_guest(guest_user)
-
- sign_in(guest_user)
- end
-
- it 'click on New issue shows new issue page' do
- visit project_path(project)
-
- click_topmenuitem("New issue")
-
- expect(page).to have_content('New Issue')
- expect(page).to have_content('Title')
- end
-
- it 'has no New merge request menu item' do
- visit project_path(project)
-
- hasnot_topmenuitem("New merge request")
- end
-
- it 'has no New project snippet menu item' do
- visit project_path(project)
-
- expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
- end
-
- it 'public project has no New merge request menu item' do
- visit project_path(public_project)
-
- hasnot_topmenuitem("New merge request")
- end
-
- it 'public project has no New project snippet menu item' do
- visit project_path(public_project)
-
- expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
- end
-
- it 'has no New subgroup menu item' do
- visit group_path(group)
-
- hasnot_topmenuitem("New subgroup")
- end
-
- it 'has no New project for group menu item' do
- visit group_path(group)
-
- expect(find('.header-new.dropdown')).not_to have_selector('.header-new-group-project')
- end
- end
-
- def click_topmenuitem(item_name)
- page.within '.header-content' do
- find('.header-new-dropdown-toggle').click
- expect(page).to have_selector('.header-new.dropdown.show', count: 1)
- click_link item_name
- end
- end
-
- def hasnot_topmenuitem(item_name)
- expect(find('.header-new.dropdown')).not_to have_content(item_name)
- end
-end
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index 0d04ed612c2..c29dfb01381 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -4,7 +4,7 @@ describe 'Help Pages' do
describe 'Get the main help page' do
shared_examples_for 'help page' do |prefix: ''|
it 'prefixes links correctly' do
- expect(page).to have_selector(%(div.documentation-index > ul a[href="#{prefix}/help/api/README.md"]))
+ expect(page).to have_selector(%(div.documentation-index > table tbody tr td a[href="#{prefix}/help/api/README.md"]))
end
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 7c591dacce5..d7531d5fcd9 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -9,7 +9,6 @@ describe 'GFM autocomplete', :js do
let(:project) { create(:project) }
let(:label) { create(:label, project: project, title: 'special+') }
let(:issue) { create(:issue, project: project) }
- let!(:project_snippet) { create(:project_snippet, project: project, title: 'code snippet') }
before do
project.add_maintainer(user)
@@ -334,16 +333,57 @@ describe 'GFM autocomplete', :js do
end
end
- it 'shows project snippets' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('$')
- end
+ shared_examples 'autocomplete suggestions' do
+ it 'suggests objects correctly' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys(object.class.reference_prefix)
+ end
+
+ page.within '.atwho-container' do
+ expect(page).to have_content(object.title)
- page.within '.atwho-container' do
- expect(page).to have_content(project_snippet.title)
+ find('ul li').click
+ end
+
+ expect(find('.new-note #note-body').value).to include(expected_body)
end
end
+ context 'issues' do
+ let(:object) { issue }
+ let(:expected_body) { object.to_reference }
+
+ it_behaves_like 'autocomplete suggestions'
+ end
+
+ context 'merge requests' do
+ let(:object) { create(:merge_request, source_project: project) }
+ let(:expected_body) { object.to_reference }
+
+ it_behaves_like 'autocomplete suggestions'
+ end
+
+ context 'project snippets' do
+ let!(:object) { create(:project_snippet, project: project, title: 'code snippet') }
+ let(:expected_body) { object.to_reference }
+
+ it_behaves_like 'autocomplete suggestions'
+ end
+
+ context 'label' do
+ let!(:object) { label }
+ let(:expected_body) { object.title }
+
+ it_behaves_like 'autocomplete suggestions'
+ end
+
+ context 'milestone' do
+ let!(:object) { create(:milestone, project: project) }
+ let(:expected_body) { object.to_reference }
+
+ it_behaves_like 'autocomplete suggestions'
+ end
+
private
def expect_to_wrap(should_wrap, item, note, value)
diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb
index ba5b80ed04b..b4b9a589ba3 100644
--- a/spec/features/issues/user_comments_on_issue_spec.rb
+++ b/spec/features/issues/user_comments_on_issue_spec.rb
@@ -40,6 +40,18 @@ describe "User comments on issue", :js do
expect(page.find('pre code').text).to eq code_block_content
end
+
+ it "does not render html content in mermaid" do
+ html_content = "<img onerror=location=`javascript\\u003aalert\\u0028document.domain\\u0029` src=x>"
+ mermaid_content = "graph LR\n B-->D(#{html_content});"
+ comment = "```mermaid\n#{mermaid_content}\n```"
+
+ add_note(comment)
+
+ wait_for_requests
+
+ expect(page.find('svg.mermaid')).to have_content html_content
+ end
end
context "when editing comments" do
diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
index ca234321235..43369f7609f 100644
--- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
@@ -1,15 +1,15 @@
require 'rails_helper'
-describe 'New issue breadcrumbs' do
+describe 'New issue breadcrumb' do
let(:project) { create(:project) }
- let(:user) { project.creator }
+ let(:user) { project.creator }
before do
sign_in(user)
- visit new_project_issue_path(project)
+ visit(new_project_issue_path(project))
end
- it 'display a link to project issues and new issue pages' do
+ it 'displays link to project issues and new issue' do
page.within '.breadcrumbs' do
expect(find_link('Issues')[:href]).to end_with(project_issues_path(project))
expect(find_link('New')[:href]).to end_with(new_project_issue_path(project))
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 4d9b8a10e04..406e80e91aa 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -8,6 +8,17 @@ describe 'Issues' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
+ shared_examples_for 'empty state with filters' do
+ it 'user sees empty state with filters' do
+ create(:issue, author: user, project: project)
+
+ visit project_issues_path(project, milestone_title: "1.0")
+
+ expect(page).to have_content('Sorry, your filter produced no results')
+ expect(page).to have_content('To widen your search, change or remove filters above')
+ end
+ end
+
describe 'while user is signed out' do
describe 'empty state' do
it 'user sees empty state' do
@@ -17,6 +28,8 @@ describe 'Issues' do
expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project.')
expect(page).to have_content('You can register or sign in to create issues for this project.')
end
+
+ it_behaves_like 'empty state with filters'
end
end
@@ -37,6 +50,8 @@ describe 'Issues' do
expect(page).to have_content('Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.')
expect(page).to have_content('New issue')
end
+
+ it_behaves_like 'empty state with filters'
end
describe 'Edit issue' do
@@ -667,6 +682,18 @@ describe 'Issues' do
expect(find('.js-issuable-selector .dropdown-toggle-text')).to have_content('bug')
end
end
+
+ context 'suggestions', :js do
+ it 'displays list of related issues' do
+ create(:issue, project: project, title: 'test issue')
+
+ visit new_project_issue_path(project)
+
+ fill_in 'issue_title', with: issue.title
+
+ expect(page).to have_selector('.suggestion-item', count: 1)
+ end
+ end
end
describe 'new issue by email' do
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index a25d701ee35..7008b361394 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -18,7 +18,7 @@ describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue)
%w[A B C D].each do |label|
- expect(page).to have_selector('svg foreignObject', text: label)
+ expect(page).to have_selector('svg text', text: label)
end
end
end
diff --git a/spec/features/merge_request/user_assigns_themselves_spec.rb b/spec/features/merge_request/user_assigns_themselves_spec.rb
index b6b38186a22..41cc7cef777 100644
--- a/spec/features/merge_request/user_assigns_themselves_spec.rb
+++ b/spec/features/merge_request/user_assigns_themselves_spec.rb
@@ -42,7 +42,7 @@ describe 'Merge request > User assigns themselves' do
visit project_merge_request_path(project, merge_request)
end
- it 'does not not show assignment link' do
+ it 'does not show assignment link' do
expect(page).not_to have_content 'Assign yourself'
end
end
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 8a16c011067..328f96e6ed7 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -50,7 +50,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
find('.line-resolve-btn').click
expect(page).to have_selector('.line-resolve-btn.is-active')
- expect(find('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(find('.line-resolve-btn')['aria-label']).to eq("Resolved by #{user.name}")
end
page.within '.diff-content' do
@@ -243,7 +243,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
resolve_button.click
wait_for_requests
- expect(resolve_button['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(resolve_button['aria-label']).to eq("Resolved by #{user.name}")
end
end
@@ -266,7 +266,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
wait_for_requests
- expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(first('.line-resolve-btn')['aria-label']).to eq("Resolved by #{user.name}")
end
expect(page).to have_content('Last updated')
@@ -285,7 +285,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
wait_for_requests
resolve_buttons.each do |button|
- expect(button['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(button['aria-label']).to eq("Resolved by #{user.name}")
end
page.within '.line-resolve-all-container' do
@@ -325,7 +325,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
end
- it 'allows user user to mark all discussions as resolved' do
+ it 'allows user to mark all discussions as resolved' do
page.all('.discussion-reply-holder', count: 2).each do |reply_holder|
page.within reply_holder do
click_button 'Resolve discussion'
@@ -357,13 +357,12 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
resolve_button.click
wait_for_requests
- expect(resolve_button['data-original-title']).to eq("Resolved by #{user.name}")
+ expect(resolve_button['aria-label']).to eq("Resolved by #{user.name}")
end
end
- it 'shows jump to next discussion button, apart from the last one' do
- expect(page).to have_selector('.discussion-reply-holder', count: 2)
- expect(page).to have_selector('.discussion-reply-holder .discussion-next-btn', count: 1)
+ it 'shows jump to next discussion button' do
+ expect(page.all('.discussion-reply-holder', count: 2)).to all(have_selector('.discussion-next-btn'))
end
it 'displays next discussion even if hidden' do
diff --git a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
index f17acb35a5a..18d204da17a 100644
--- a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
@@ -1,15 +1,15 @@
require 'rails_helper'
-describe 'New merge request breadcrumbs' do
+describe 'New merge request breadcrumb' do
let(:project) { create(:project, :repository) }
- let(:user) { project.creator }
+ let(:user) { project.creator }
before do
sign_in(user)
- visit project_new_merge_request_path(project)
+ visit(project_new_merge_request_path(project))
end
- it 'display a link to project merge requests and new merge request pages' do
+ it 'displays link to project merge requests and new merge request' do
page.within '.breadcrumbs' do
expect(find_link('Merge Requests')[:href]).to end_with(project_merge_requests_path(project))
expect(find_link('New')[:href]).to end_with(project_new_merge_request_path(project))
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index 74290c0fff9..3e40179ad9a 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -65,7 +65,20 @@ describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request)
wait_for_requests
- expect(page).to have_content("Deploying to #{environment.name}")
+ expect(page).to have_content("Will deploy to #{environment.name}")
+ expect(page).not_to have_css('.js-deploy-time')
+ end
+ end
+
+ context 'when deployment was cancelled' do
+ let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, :canceled, environment: environment, sha: sha, ref: ref, deployable: build) }
+
+ it 'displays that the environment name' do
+ visit project_merge_request_path(project, merge_request)
+ wait_for_requests
+
+ expect(page).to have_content("Failed to deploy to #{environment.name}")
expect(page).not_to have_css('.js-deploy-time')
end
end
diff --git a/spec/features/merge_request/user_sees_discussions_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb
index 7b8c3bacfe2..4ab9a87ad4b 100644
--- a/spec/features/merge_request/user_sees_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_discussions_spec.rb
@@ -53,13 +53,11 @@ describe 'Merge request > User sees discussions', :js do
shared_examples 'a functional discussion' do
let(:discussion_id) { note.discussion_id(merge_request) }
- # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
- xit 'is displayed' do
+ it 'is displayed' do
expect(page).to have_css(".discussion[data-discussion-id='#{discussion_id}']")
end
- # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
- xit 'can be replied to' do
+ it 'can be replied to' do
within(".discussion[data-discussion-id='#{discussion_id}']") do
click_button 'Reply...'
fill_in 'note[note]', with: 'Test!'
@@ -74,16 +72,21 @@ describe 'Merge request > User sees discussions', :js do
visit project_merge_request_path(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
+ # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+ # 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'
+
+ it 'displays correct header' do
+ expect(page).to have_content "started a discussion on commit #{note.commit_id[0...7]}"
+ end
end
end
end
diff --git a/spec/features/merge_request/user_sees_empty_state_spec.rb b/spec/features/merge_request/user_sees_empty_state_spec.rb
index 482f31b02d4..012bfd6e458 100644
--- a/spec/features/merge_request/user_sees_empty_state_spec.rb
+++ b/spec/features/merge_request/user_sees_empty_state_spec.rb
@@ -19,12 +19,20 @@ describe 'Merge request > User sees empty state' do
context 'if there are merge requests' do
before do
create(:merge_request, source_project: project)
-
- visit project_merge_requests_path(project)
end
it 'does not show an empty state' do
+ visit project_merge_requests_path(project)
+
expect(page).not_to have_selector('.empty-state')
end
+
+ it 'shows empty state when filter results empty' do
+ visit project_merge_requests_path(project, milestone_title: "1.0")
+
+ expect(page).to have_selector('.empty-state')
+ expect(page).to have_content('Sorry, your filter produced no results')
+ expect(page).to have_content('To widen your search, change or remove filters above')
+ end
end
end
diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index 92db4f44098..f7512294bef 100644
--- a/spec/features/merge_request/user_sees_versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -66,7 +66,6 @@ describe 'Merge request > User sees versions', :js do
it 'shows comments that were last relevant at that version' do
expect(page).to have_content '5 changed files'
- expect(page).to have_content 'Not all comments are displayed'
position = Gitlab::Diff::Position.new(
old_path: ".gitmodules",
@@ -112,7 +111,6 @@ describe 'Merge request > User sees versions', :js do
)
expect(page).to have_content '4 changed files'
expect(page).to have_content '15 additions 6 deletions'
- expect(page).to have_content 'Not all comments are displayed'
position = Gitlab::Diff::Position.new(
old_path: ".gitmodules",
diff --git a/spec/features/milestones/user_promotes_milestone_spec.rb b/spec/features/milestones/user_promotes_milestone_spec.rb
new file mode 100644
index 00000000000..df1bc502134
--- /dev/null
+++ b/spec/features/milestones/user_promotes_milestone_spec.rb
@@ -0,0 +1,32 @@
+require 'rails_helper'
+
+describe 'User promotes milestone' do
+ set(:group) { create(:group) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project, namespace: group) }
+ set(:milestone) { create(:milestone, project: project) }
+
+ context 'when user can admin group milestones' do
+ before do
+ group.add_developer(user)
+ sign_in(user)
+ visit(project_milestones_path(project))
+ end
+
+ it "shows milestone promote button" do
+ expect(page).to have_selector('.js-promote-project-milestone-button')
+ end
+ end
+
+ context 'when user cannot admin group milestones' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ visit(project_milestones_path(project))
+ end
+
+ it "does not show milestone promote button" do
+ expect(page).not_to have_selector('.js-promote-project-milestone-button')
+ end
+ end
+end
diff --git a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
new file mode 100644
index 00000000000..d3906ea73bd
--- /dev/null
+++ b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+describe 'New project milestone breadcrumb' do
+ let(:project) { create(:project) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:user) { project.creator }
+
+ before do
+ sign_in(user)
+ visit(new_project_milestone_path(project))
+ end
+
+ it 'displays link to project milestones and new project milestone' do
+ page.within '.breadcrumbs' do
+ expect(find_link('Milestones')[:href]).to end_with(project_milestones_path(project))
+ expect(find_link('New')[:href]).to end_with(new_project_milestone_path(project))
+ end
+ end
+end
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index 534cfe1eb12..2159adf49fc 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -4,10 +4,9 @@ describe 'User browses commits' do
include RepoHelpers
let(:user) { create(:user) }
- let(:project) { create(:project, :repository, namespace: user.namespace) }
+ let(:project) { create(:project, :public, :repository, namespace: user.namespace) }
before do
- project.add_maintainer(user)
sign_in(user)
end
@@ -127,6 +126,26 @@ describe 'User browses commits' do
.and have_selector('entry summary', text: commit.description[0..10].delete("\r\n"))
end
+ context 'when a commit links to a confidential issue' do
+ let(:confidential_issue) { create(:issue, confidential: true, title: 'Secret issue!', project: project) }
+
+ before do
+ project.repository.create_file(user, 'dummy-file', 'dummy content',
+ branch_name: 'feature',
+ message: "Linking #{confidential_issue.to_reference}")
+ end
+
+ context 'when the user cannot see confidential issues but was cached with a link', :use_clean_rails_memory_store_fragment_caching do
+ it 'does not render the confidential issue' do
+ visit project_commits_path(project, 'feature')
+ sign_in(create(:user))
+ visit project_commits_path(project, 'feature')
+
+ expect(page).not_to have_link(href: project_issue_path(project, confidential_issue))
+ end
+ end
+ end
+
context 'master branch' do
before do
visit_commits_page
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 9772a7bacac..a8a3b6910fb 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -165,8 +165,14 @@ describe 'Environment' do
context 'web terminal', :js do
before do
- # Stub #terminals as it causes js-enabled feature specs to render the page incorrectly
- allow_any_instance_of(Environment).to receive(:terminals) { nil }
+ # Stub #terminals as it causes js-enabled feature specs to
+ # render the page incorrectly
+ #
+ # In EE we have to stub EE::Environment since it overwrites
+ # the "terminals" method.
+ allow_any_instance_of(defined?(EE) ? EE::Environment : Environment)
+ .to receive(:terminals) { nil }
+
visit terminal_project_environment_path(project, environment)
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 99a7fbb63bd..d7c4abffddd 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -596,7 +596,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'shows delayed job', :js do
expect(page).to have_content('This is a delayed job to run in')
- expect(page).to have_content("This job will automatically run after it's timer finishes.")
+ expect(page).to have_content("This job will automatically run after its timer finishes.")
expect(page).to have_link('Unschedule job')
end
@@ -754,7 +754,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because no runners are active' do
expect(page).to have_css('.js-stuck-no-active-runner')
- expect(page).to have_content("This job is stuck, because you don't have any active runners that can run this job.")
+ expect(page).to have_content("This job is stuck because you don't have any active runners that can run this job.")
end
end
@@ -764,7 +764,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because of no runners with the specified tags' do
expect(page).to have_css('.js-stuck-with-tags')
- expect(page).to have_content("This job is stuck, because you don't have any active runners online with any of these tags assigned to them:")
+ expect(page).to have_content("This job is stuck because you don't have any active runners online with any of these tags assigned to them:")
end
end
@@ -774,7 +774,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because of no runners with the specified tags' do
expect(page).to have_css('.js-stuck-with-tags')
- expect(page).to have_content("This job is stuck, because you don't have any active runners online with any of these tags assigned to them:")
+ expect(page).to have_content("This job is stuck because you don't have any active runners online with any of these tags assigned to them:")
end
end
@@ -783,7 +783,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because not runners are available' do
expect(page).to have_css('.js-stuck-no-active-runner')
- expect(page).to have_content("This job is stuck, because you don't have any active runners that can run this job.")
+ expect(page).to have_content("This job is stuck because you don't have any active runners that can run this job.")
end
end
@@ -793,7 +793,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders message about job being stuck because runners are offline' do
expect(page).to have_css('.js-stuck-no-runners')
- expect(page).to have_content("This job is stuck, because the project doesn't have any runners online assigned to it.")
+ expect(page).to have_content("This job is stuck because the project doesn't have any runners online assigned to it.")
end
end
end
diff --git a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
new file mode 100644
index 00000000000..0c0501f438a
--- /dev/null
+++ b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe 'New project label breadcrumb' do
+ let(:project) { create(:project) }
+ let(:user) { project.creator }
+
+ before do
+ sign_in(user)
+ visit(project_labels_path(project))
+ end
+
+ it 'displays link to project labels and new project label' do
+ page.within '.breadcrumbs' do
+ expect(find_link('Labels')[:href]).to end_with(project_labels_path(project))
+ end
+ end
+end
diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
index b6e65fcbda1..84de6858d5f 100644
--- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
@@ -10,19 +10,19 @@ describe 'Projects > Settings > User manages merge request settings' do
end
it 'shows "Merge commit" strategy' do
- page.within '.merge-requests-feature' do
+ page.within '#js-merge-request-settings' do
expect(page).to have_content 'Merge commit'
end
end
it 'shows "Merge commit with semi-linear history " strategy' do
- page.within '.merge-requests-feature' do
+ page.within '#js-merge-request-settings' do
expect(page).to have_content 'Merge commit with semi-linear history'
end
end
it 'shows "Fast-forward merge" strategy' do
- page.within '.merge-requests-feature' do
+ page.within '#js-merge-request-settings' do
expect(page).to have_content 'Fast-forward merge'
end
end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index b30286e4446..48a0d675f2d 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -85,7 +85,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "test"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Test").and have_content("Create Page")
+ expect(page).to have_content("test").and have_content("Create Page")
end
click_link("Home")
@@ -97,7 +97,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "api"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Create").and have_content("Api")
+ expect(page).to have_content("Create").and have_content("api")
end
click_link("Home")
@@ -109,7 +109,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "raketasks"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Create").and have_content("Rake")
+ expect(page).to have_content("Create").and have_content("rake")
end
end
@@ -157,7 +157,7 @@ describe "User creates wiki page" do
expect(page).to have_field("wiki[message]", with: "Create home")
end
- it "creates a page from from the home page" do
+ it "creates a page from the home page" do
page.within(".wiki-form") do
fill_in(:wiki_content, with: "My awesome wiki!")
@@ -200,7 +200,7 @@ describe "User creates wiki page" do
click_button("Create page")
end
- expect(page).to have_content("Foo")
+ expect(page).to have_content("foo")
.and have_content("Last edited by #{user.name}")
.and have_content("My awesome wiki!")
end
@@ -215,7 +215,7 @@ describe "User creates wiki page" do
end
# Commit message field should have correct value.
- expect(page).to have_field("wiki[message]", with: "Create spaces in the name")
+ expect(page).to have_field("wiki[message]", with: "Create Spaces in the name")
page.within(".wiki-form") do
fill_in(:wiki_content, with: "My awesome wiki!")
@@ -246,7 +246,7 @@ describe "User creates wiki page" do
click_button("Create page")
end
- expect(page).to have_content("Hyphens in the name")
+ expect(page).to have_content("hyphens in the name")
.and have_content("Last edited by #{user.name}")
.and have_content("My awesome wiki!")
end
@@ -293,7 +293,7 @@ describe "User creates wiki page" do
click_button("Create page")
end
- expect(page).to have_content("Foo")
+ expect(page).to have_content("foo")
.and have_content("Last edited by #{user.name}")
.and have_content("My awesome wiki!")
end
@@ -311,7 +311,7 @@ describe "User creates wiki page" do
it 'renders a default sidebar when there is no customized sidebar' do
visit(project_wikis_path(project))
- expect(page).to have_content('Another')
+ expect(page).to have_content('another')
expect(page).to have_content('More Pages')
end
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index 2ce5ee0e87d..f76e577b0d6 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -1,233 +1,223 @@
require 'spec_helper'
describe 'User updates wiki page' do
- shared_examples 'wiki page user update' do
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ context 'when wiki is empty' do
before do
- project.add_maintainer(user)
- sign_in(user)
+ visit(project_wikis_path(project))
+ click_link "Create your first page"
end
- context 'when wiki is empty' do
- before do
- visit(project_wikis_path(project))
- click_link "Create your first page"
- end
-
- context 'in a user namespace' do
- let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
+ context 'in a user namespace' do
+ let(:project) { create(:project, :wiki_repo) }
- it 'redirects back to the home edit page' do
- page.within(:css, '.wiki-form .form-actions') do
- click_on('Cancel')
- end
-
- expect(current_path).to eq project_wiki_path(project, :home)
+ it 'redirects back to the home edit page' do
+ page.within(:css, '.wiki-form .form-actions') do
+ click_on('Cancel')
end
- it 'updates a page that has a path', :js do
- click_on('New page')
+ expect(current_path).to eq project_wiki_path(project, :home)
+ end
- page.within('#modal-new-wiki') do
- fill_in(:new_wiki_path, with: 'one/two/three-test')
- click_on('Create page')
- end
+ it 'updates a page that has a path', :js do
+ click_on('New page')
- page.within '.wiki-form' do
- fill_in(:wiki_content, with: 'wiki content')
- click_on('Create page')
- end
+ page.within('#modal-new-wiki') do
+ fill_in(:new_wiki_path, with: 'one/two/three-test')
+ click_on('Create page')
+ end
- expect(current_path).to include('one/two/three-test')
- expect(find('.wiki-pages')).to have_content('Three')
+ page.within '.wiki-form' do
+ fill_in(:wiki_content, with: 'wiki content')
+ click_on('Create page')
+ end
- first(:link, text: 'Three').click
+ expect(current_path).to include('one/two/three-test')
+ expect(find('.wiki-pages')).to have_content('three')
- expect(find('.nav-text')).to have_content('Three')
+ first(:link, text: 'three').click
- click_on('Edit')
+ expect(find('.nav-text')).to have_content('three')
- expect(current_path).to include('one/two/three-test')
- expect(page).to have_content('Edit Page')
+ click_on('Edit')
- fill_in('Content', with: 'Updated Wiki Content')
- click_on('Save changes')
+ expect(current_path).to include('one/two/three-test')
+ expect(page).to have_content('Edit Page')
- expect(page).to have_content('Updated Wiki Content')
- end
+ fill_in('Content', with: 'Updated Wiki Content')
+ click_on('Save changes')
- it_behaves_like 'wiki file attachments'
+ expect(page).to have_content('Updated Wiki Content')
end
- end
- context 'when wiki is not empty' do
- let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) }
- let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: 'home', content: 'Home page' }) }
+ it_behaves_like 'wiki file attachments'
+ end
+ end
- before do
- visit(project_wikis_path(project))
+ context 'when wiki is not empty' do
+ let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) }
+ let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: 'home', content: 'Home page' }) }
- click_link('Edit')
- end
+ before do
+ visit(project_wikis_path(project))
- context 'in a user namespace' do
- let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
+ click_link('Edit')
+ end
- it 'updates a page' do
- # Commit message field should have correct value.
- expect(page).to have_field('wiki[message]', with: 'Update home')
+ context 'in a user namespace' do
+ let(:project) { create(:project, :wiki_repo) }
- fill_in(:wiki_content, with: 'My awesome wiki!')
- click_button('Save changes')
+ it 'updates a page' do
+ # Commit message field should have correct value.
+ expect(page).to have_field('wiki[message]', with: 'Update home')
- expect(page).to have_content('Home')
- expect(page).to have_content("Last edited by #{user.name}")
- expect(page).to have_content('My awesome wiki!')
- end
+ fill_in(:wiki_content, with: 'My awesome wiki!')
+ click_button('Save changes')
- it 'shows a validation error message' do
- fill_in(:wiki_content, with: '')
- click_button('Save changes')
+ expect(page).to have_content('Home')
+ expect(page).to have_content("Last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
- expect(page).to have_selector('.wiki-form')
- expect(page).to have_content('Edit Page')
- expect(page).to have_content('The form contains the following error:')
- expect(page).to have_content("Content can't be blank")
- expect(find('textarea#wiki_content').value).to eq('')
- end
+ it 'shows a validation error message' do
+ fill_in(:wiki_content, with: '')
+ click_button('Save changes')
- it 'shows the emoji autocompletion dropdown', :js do
- find('#wiki_content').native.send_keys('')
- fill_in(:wiki_content, with: ':')
+ expect(page).to have_selector('.wiki-form')
+ expect(page).to have_content('Edit Page')
+ expect(page).to have_content('The form contains the following error:')
+ expect(page).to have_content("Content can't be blank")
+ expect(find('textarea#wiki_content').value).to eq('')
+ end
- expect(page).to have_selector('.atwho-view')
- end
+ it 'shows the emoji autocompletion dropdown', :js do
+ find('#wiki_content').native.send_keys('')
+ fill_in(:wiki_content, with: ':')
- it 'shows the error message' do
- wiki_page.update(content: 'Update')
+ expect(page).to have_selector('.atwho-view')
+ end
- click_button('Save changes')
+ it 'shows the error message' do
+ wiki_page.update(content: 'Update')
- expect(page).to have_content('Someone edited the page the same time you did.')
- end
+ click_button('Save changes')
- it 'updates a page' do
- fill_in('Content', with: 'Updated Wiki Content')
- click_on('Save changes')
+ expect(page).to have_content('Someone edited the page the same time you did.')
+ end
- expect(page).to have_content('Updated Wiki Content')
- end
+ it 'updates a page' do
+ fill_in('Content', with: 'Updated Wiki Content')
+ click_on('Save changes')
- it 'cancels editing of a page' do
- page.within(:css, '.wiki-form .form-actions') do
- click_on('Cancel')
- end
+ expect(page).to have_content('Updated Wiki Content')
+ end
- expect(current_path).to eq(project_wiki_path(project, wiki_page))
+ it 'cancels editing of a page' do
+ page.within(:css, '.wiki-form .form-actions') do
+ click_on('Cancel')
end
- it_behaves_like 'wiki file attachments'
+ expect(current_path).to eq(project_wiki_path(project, wiki_page))
end
- context 'in a group namespace' do
- let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
+ it_behaves_like 'wiki file attachments'
+ end
- it 'updates a page' do
- # Commit message field should have correct value.
- expect(page).to have_field('wiki[message]', with: 'Update home')
+ context 'in a group namespace' do
+ let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
- fill_in(:wiki_content, with: 'My awesome wiki!')
+ it 'updates a page' do
+ # Commit message field should have correct value.
+ expect(page).to have_field('wiki[message]', with: 'Update home')
- click_button('Save changes')
+ fill_in(:wiki_content, with: 'My awesome wiki!')
- expect(page).to have_content('Home')
- expect(page).to have_content("Last edited by #{user.name}")
- expect(page).to have_content('My awesome wiki!')
- end
+ click_button('Save changes')
- it_behaves_like 'wiki file attachments'
+ expect(page).to have_content('Home')
+ expect(page).to have_content("Last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
end
- end
- context 'when the page is in a subdir' do
- let!(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
- let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) }
- let(:page_name) { 'page_name' }
- let(:page_dir) { "foo/bar/#{page_name}" }
- let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: page_dir, content: 'Home page' }) }
+ it_behaves_like 'wiki file attachments'
+ end
+ end
- before do
- visit(project_wiki_edit_path(project, wiki_page))
- end
+ context 'when the page is in a subdir' do
+ let!(:project) { create(:project, :wiki_repo) }
+ let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) }
+ let(:page_name) { 'page_name' }
+ let(:page_dir) { "foo/bar/#{page_name}" }
+ let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: page_dir, content: 'Home page' }) }
- it 'moves the page to the root folder', :skip_gitaly_mock do
- fill_in(:wiki_title, with: "/#{page_name}")
+ before do
+ visit(project_wiki_edit_path(project, wiki_page))
+ end
- click_button('Save changes')
+ it 'moves the page to the root folder' do
+ fill_in(:wiki_title, with: "/#{page_name}")
- expect(current_path).to eq(project_wiki_path(project, page_name))
- end
+ click_button('Save changes')
- it 'moves the page to other dir' do
- new_page_dir = "foo1/bar1/#{page_name}"
+ expect(current_path).to eq(project_wiki_path(project, page_name))
+ end
- fill_in(:wiki_title, with: new_page_dir)
+ it 'moves the page to other dir' do
+ new_page_dir = "foo1/bar1/#{page_name}"
- click_button('Save changes')
+ fill_in(:wiki_title, with: new_page_dir)
- expect(current_path).to eq(project_wiki_path(project, new_page_dir))
- end
+ click_button('Save changes')
- it 'remains in the same place if title has not changed' do
- original_path = project_wiki_path(project, wiki_page)
+ expect(current_path).to eq(project_wiki_path(project, new_page_dir))
+ end
- fill_in(:wiki_title, with: page_name)
+ it 'remains in the same place if title has not changed' do
+ original_path = project_wiki_path(project, wiki_page)
- click_button('Save changes')
+ fill_in(:wiki_title, with: page_name)
- expect(current_path).to eq(original_path)
- end
+ click_button('Save changes')
- it 'can be moved to a different dir with a different name' do
- new_page_dir = "foo1/bar1/new_page_name"
+ expect(current_path).to eq(original_path)
+ end
- fill_in(:wiki_title, with: new_page_dir)
+ it 'can be moved to a different dir with a different name' do
+ new_page_dir = "foo1/bar1/new_page_name"
- click_button('Save changes')
+ fill_in(:wiki_title, with: new_page_dir)
- expect(current_path).to eq(project_wiki_path(project, new_page_dir))
- end
+ click_button('Save changes')
- it 'can be renamed and moved to the root folder' do
- new_name = 'new_page_name'
+ expect(current_path).to eq(project_wiki_path(project, new_page_dir))
+ end
- fill_in(:wiki_title, with: "/#{new_name}")
+ it 'can be renamed and moved to the root folder' do
+ new_name = 'new_page_name'
- click_button('Save changes')
+ fill_in(:wiki_title, with: "/#{new_name}")
- expect(current_path).to eq(project_wiki_path(project, new_name))
- end
+ click_button('Save changes')
- it 'squishes the title before creating the page' do
- new_page_dir = " foo1 / bar1 / #{page_name} "
+ expect(current_path).to eq(project_wiki_path(project, new_name))
+ end
- fill_in(:wiki_title, with: new_page_dir)
+ it 'squishes the title before creating the page' do
+ new_page_dir = " foo1 / bar1 / #{page_name} "
- click_button('Save changes')
+ fill_in(:wiki_title, with: new_page_dir)
- expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}"))
- end
+ click_button('Save changes')
- it_behaves_like 'wiki file attachments'
+ expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}"))
end
- end
-
- context 'when Gitaly is enabled' do
- it_behaves_like 'wiki page user update'
- end
- context 'when Gitaly is disabled', :skip_gitaly_mock do
- it_behaves_like 'wiki page user update'
+ it_behaves_like 'wiki file attachments'
end
end
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index 4b974a3ca10..d4691b669c1 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -1,174 +1,164 @@
require 'spec_helper'
describe 'User views a wiki page' do
- shared_examples 'wiki page user view' do
- include WikiHelpers
-
- let(:user) { create(:user) }
- let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
- let(:path) { 'image.png' }
- let(:wiki_page) do
- create(:wiki_page,
- wiki: project.wiki,
- attrs: { title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})" })
- end
+ include WikiHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
+ let(:path) { 'image.png' }
+ let(:wiki_page) do
+ create(:wiki_page,
+ wiki: project.wiki,
+ attrs: { title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})" })
+ end
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
- context 'when wiki is empty' do
- before do
- visit(project_wikis_path(project))
- click_link "Create your first page"
+ context 'when wiki is empty' do
+ before do
+ visit(project_wikis_path(project))
+ click_link "Create your first page"
- click_on('New page')
+ click_on('New page')
- page.within('#modal-new-wiki') do
- fill_in(:new_wiki_path, with: 'one/two/three-test')
- click_on('Create page')
- end
+ page.within('#modal-new-wiki') do
+ fill_in(:new_wiki_path, with: 'one/two/three-test')
+ click_on('Create page')
+ end
- page.within('.wiki-form') do
- fill_in(:wiki_content, with: 'wiki content')
- click_on('Create page')
- end
+ page.within('.wiki-form') do
+ fill_in(:wiki_content, with: 'wiki content')
+ click_on('Create page')
end
+ end
- it 'shows the history of a page that has a path', :js do
- expect(current_path).to include('one/two/three-test')
+ it 'shows the history of a page that has a path', :js do
+ expect(current_path).to include('one/two/three-test')
- first(:link, text: 'Three').click
- click_on('Page history')
+ first(:link, text: 'three').click
+ click_on('Page history')
- expect(current_path).to include('one/two/three-test')
+ expect(current_path).to include('one/two/three-test')
- page.within(:css, '.nav-text') do
- expect(page).to have_content('History')
- end
+ page.within(:css, '.nav-text') do
+ expect(page).to have_content('History')
end
+ end
- it 'shows an old version of a page', :js do
- expect(current_path).to include('one/two/three-test')
- expect(find('.wiki-pages')).to have_content('Three')
-
- first(:link, text: 'Three').click
-
- expect(find('.nav-text')).to have_content('Three')
+ it 'shows an old version of a page', :js do
+ expect(current_path).to include('one/two/three-test')
+ expect(find('.wiki-pages')).to have_content('three')
- click_on('Edit')
+ first(:link, text: 'three').click
- expect(current_path).to include('one/two/three-test')
- expect(page).to have_content('Edit Page')
+ expect(find('.nav-text')).to have_content('three')
- fill_in('Content', with: 'Updated Wiki Content')
+ click_on('Edit')
- click_on('Save changes')
- click_on('Page history')
+ expect(current_path).to include('one/two/three-test')
+ expect(page).to have_content('Edit Page')
- page.within(:css, '.nav-text') do
- expect(page).to have_content('History')
- end
+ fill_in('Content', with: 'Updated Wiki Content')
- find('a[href*="?version_id"]')
- end
- end
-
- context 'when a page does not have history' do
- before do
- visit(project_wiki_path(project, wiki_page))
- end
+ click_on('Save changes')
+ click_on('Page history')
- it 'shows all the pages' do
- expect(page).to have_content(user.name)
- expect(find('.wiki-pages')).to have_content(wiki_page.title.capitalize)
+ page.within(:css, '.nav-text') do
+ expect(page).to have_content('History')
end
- context 'shows a file stored in a page' do
- let(:path) { upload_file_to_wiki(project, user, 'dk.png') }
+ find('a[href*="?version_id"]')
+ end
+ end
- it do
- expect(page).to have_xpath("//img[@data-src='#{project.wiki.wiki_base_path}/#{path}']")
- expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/#{path}")
+ context 'when a page does not have history' do
+ before do
+ visit(project_wiki_path(project, wiki_page))
+ end
- click_on('image')
+ it 'shows all the pages' do
+ expect(page).to have_content(user.name)
+ expect(find('.wiki-pages')).to have_content(wiki_page.title.capitalize)
+ end
- expect(current_path).to match("wikis/#{path}")
- expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
- end
- end
+ context 'shows a file stored in a page' do
+ let(:path) { upload_file_to_wiki(project, user, 'dk.png') }
- it 'shows the creation page if file does not exist' do
+ it do
+ expect(page).to have_xpath("//img[@data-src='#{project.wiki.wiki_base_path}/#{path}']")
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/#{path}")
click_on('image')
expect(current_path).to match("wikis/#{path}")
- expect(page).to have_content('New Wiki Page')
- expect(page).to have_content('Create page')
+ expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
end
end
- context 'when a page has history' do
- before do
- wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)')
- end
+ it 'shows the creation page if file does not exist' do
+ expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/#{path}")
- it 'shows the page history' do
- visit(project_wiki_path(project, wiki_page))
+ click_on('image')
- expect(page).to have_selector('a.btn', text: 'Edit')
+ expect(current_path).to match("wikis/#{path}")
+ expect(page).to have_content('New Wiki Page')
+ expect(page).to have_content('Create page')
+ end
+ end
- click_on('Page history')
+ context 'when a page has history' do
+ before do
+ wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)')
+ end
- expect(page).to have_content(user.name)
- expect(page).to have_content("#{user.username} created page: home")
- expect(page).to have_content('updated home')
- end
+ it 'shows the page history' do
+ visit(project_wiki_path(project, wiki_page))
- it 'does not show the "Edit" button' do
- visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id))
+ expect(page).to have_selector('a.btn', text: 'Edit')
- expect(page).not_to have_selector('a.btn', text: 'Edit')
- end
+ click_on('Page history')
+
+ expect(page).to have_content(user.name)
+ expect(page).to have_content("#{user.username} created page: home")
+ expect(page).to have_content('updated home')
end
- context 'when page has invalid content encoding' do
- let(:content) { 'whatever'.force_encoding('ISO-8859-1') }
+ it 'does not show the "Edit" button' do
+ visit(project_wiki_path(project, wiki_page, version_id: wiki_page.versions.last.id))
- before do
- allow(Gitlab::EncodingHelper).to receive(:encode!).and_return(content)
+ expect(page).not_to have_selector('a.btn', text: 'Edit')
+ end
+ end
- visit(project_wiki_path(project, wiki_page))
- end
+ context 'when page has invalid content encoding' do
+ let(:content) { 'whatever'.force_encoding('ISO-8859-1') }
- it 'does not show "Edit" button' do
- expect(page).not_to have_selector('a.btn', text: 'Edit')
- end
+ before do
+ allow(Gitlab::EncodingHelper).to receive(:encode!).and_return(content)
- it 'shows error' do
- page.within(:css, '.flash-notice') do
- expect(page).to have_content('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.')
- end
- end
+ visit(project_wiki_path(project, wiki_page))
end
- it 'opens a default wiki page', :js do
- visit(project_path(project))
-
- find('.shortcuts-wiki').click
- click_link "Create your first page"
+ it 'does not show "Edit" button' do
+ expect(page).not_to have_selector('a.btn', text: 'Edit')
+ end
- expect(page).to have_content('Home · Create Page')
+ it 'shows error' do
+ page.within(:css, '.flash-notice') do
+ expect(page).to have_content('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.')
+ end
end
end
- context 'when Gitaly is enabled' do
- it_behaves_like 'wiki page user view'
- end
+ it 'opens a default wiki page', :js do
+ visit(project_path(project))
+
+ find('.shortcuts-wiki').click
+ click_link "Create your first page"
- context 'when Gitaly is disabled', :skip_gitaly_mock do
- it_behaves_like 'wiki page user view'
+ expect(page).to have_content('Home · Create Page')
end
end
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
index ef0e55a1468..e2b3444272e 100644
--- a/spec/features/signed_commits_spec.rb
+++ b/spec/features/signed_commits_spec.rb
@@ -1,25 +1,22 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'GPG signed commits', :js do
- set(:ref) { :'2d1096e3a0ecf1d2baf6dee036cc80775d4940ba' }
- let(:project) { create(:project, :repository) }
+describe 'GPG signed commits' do
+ let(:project) { create(:project, :public, :repository) }
it 'changes from unverified to verified when the user changes his email to match the gpg key' do
- user = create :user, email: 'unrelated.user@example.org'
- project.add_maintainer(user)
+ ref = GpgHelpers::SIGNED_AND_AUTHORED_SHA
+ user = create(:user, email: 'unrelated.user@example.org')
perform_enqueued_jobs do
create :gpg_key, key: GpgHelpers::User1.public_key, user: user
end
- sign_in(user)
-
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, ref)
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).not_to have_content 'Verified'
- end
+ expect(page).to have_link 'Unverified'
+ expect(page).not_to have_link 'Verified'
# user changes his email which makes the gpg key verified
perform_enqueued_jobs do
@@ -27,41 +24,33 @@ describe 'GPG signed commits', :js do
user.update!(email: GpgHelpers::User1.emails.first)
end
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, ref)
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).to have_content 'Verified'
- end
+ expect(page).not_to have_link 'Unverified'
+ expect(page).to have_link 'Verified'
end
it 'changes from unverified to verified when the user adds the missing gpg key' do
- user = create :user, email: GpgHelpers::User1.emails.first
- project.add_maintainer(user)
+ ref = GpgHelpers::SIGNED_AND_AUTHORED_SHA
+ user = create(:user, email: GpgHelpers::User1.emails.first)
- sign_in(user)
+ visit project_commit_path(project, ref)
- visit project_commits_path(project, ref)
-
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).not_to have_content 'Verified'
- end
+ expect(page).to have_link 'Unverified'
+ expect(page).not_to have_link 'Verified'
# user adds the gpg key which makes the signature valid
perform_enqueued_jobs do
create :gpg_key, key: GpgHelpers::User1.public_key, user: user
end
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, ref)
- within '#commits-list' do
- expect(page).to have_content 'Unverified'
- expect(page).to have_content 'Verified'
- end
+ expect(page).not_to have_link 'Unverified'
+ expect(page).to have_link 'Verified'
end
- context 'shows popover badges' do
+ context 'shows popover badges', :js do
let(:user_1) do
create :user, email: GpgHelpers::User1.emails.first, username: 'nannie.bernhard', name: 'Nannie Bernhard'
end
@@ -85,19 +74,10 @@ describe 'GPG signed commits', :js do
end
end
- before do
- user = create :user
- project.add_maintainer(user)
-
- sign_in(user)
- end
-
it 'unverified signature' do
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)
- within(find('.commit', text: 'signed commit by bette cartwright')) do
- click_on 'Unverified'
- end
+ click_on 'Unverified'
within '.popover' do
expect(page).to have_content 'This commit was signed with an unverified signature.'
@@ -108,11 +88,9 @@ describe 'GPG signed commits', :js do
it 'unverified signature: user email does not match the committer email, but is the same user' do
user_2_key
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::DIFFERING_EMAIL_SHA)
- within(find('.commit', text: 'signed and authored commit by bette cartwright, different email')) do
- click_on 'Unverified'
- end
+ click_on 'Unverified'
within '.popover' do
expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not verified to belong to the same user.'
@@ -125,11 +103,9 @@ describe 'GPG signed commits', :js do
it 'unverified signature: user email does not match the committer email' do
user_2_key
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)
- within(find('.commit', text: 'signed commit by bette cartwright')) do
- click_on 'Unverified'
- end
+ click_on 'Unverified'
within '.popover' do
expect(page).to have_content "This commit was signed with a different user's verified signature."
@@ -142,11 +118,9 @@ describe 'GPG signed commits', :js do
it 'verified and the gpg user has a gitlab profile' do
user_1_key
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
- within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
- click_on 'Verified'
- end
+ click_on 'Verified'
within '.popover' do
expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.'
@@ -159,20 +133,16 @@ describe 'GPG signed commits', :js do
it "verified and the gpg user's profile doesn't exist anymore" do
user_1_key
- visit project_commits_path(project, ref)
+ visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
# wait for the signature to get generated
- within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
- expect(page).to have_content 'Verified'
- end
+ expect(page).to have_link 'Verified'
user_1.destroy!
refresh
- within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do
- click_on 'Verified'
- end
+ click_on 'Verified'
within '.popover' do
expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.'
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index c0488c83bd8..515f6f70b99 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -256,19 +256,51 @@ describe IssuesFinder do
create(:label_link, label: label2, target: issue2)
end
- it 'returns the unique issues with any of those labels' do
+ it 'returns the unique issues with all those labels' do
+ expect(issues).to contain_exactly(issue2)
+ end
+ end
+
+ context 'filtering by a label that includes any or none in the title' do
+ let(:params) { { label_name: [label.title, label2.title].join(',') } }
+ let(:label) { create(:label, title: 'any foo', project: project2) }
+ let(:label2) { create(:label, title: 'bar none', project: project2) }
+
+ it 'returns the unique issues with all those labels' do
+ create(:label_link, label: label2, target: issue2)
+
expect(issues).to contain_exactly(issue2)
end
end
context 'filtering by no label' do
- let(:params) { { label_name: Label::None.title } }
+ let(:params) { { label_name: described_class::FILTER_NONE } }
it 'returns issues with no labels' do
expect(issues).to contain_exactly(issue1, issue3, issue4)
end
end
+ context 'filtering by legacy No+Label' do
+ let(:params) { { label_name: Label::NONE } }
+
+ it 'returns issues with no labels' do
+ expect(issues).to contain_exactly(issue1, issue3, issue4)
+ end
+ end
+
+ context 'filtering by any label' do
+ let(:params) { { label_name: described_class::FILTER_ANY } }
+
+ it 'returns issues that have one or more label' do
+ 2.times do
+ create(:label_link, label: create(:label, project: project2), target: issue3)
+ end
+
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
+ end
+
context 'filtering by issue term' do
let(:params) { { search: 'git' } }
diff --git a/spec/finders/pipeline_schedules_finder_spec.rb b/spec/finders/pipeline_schedules_finder_spec.rb
index b9538649b3f..2fefa0280d1 100644
--- a/spec/finders/pipeline_schedules_finder_spec.rb
+++ b/spec/finders/pipeline_schedules_finder_spec.rb
@@ -12,7 +12,7 @@ describe PipelineSchedulesFinder do
context 'when the scope is nil' do
let(:params) { { scope: nil } }
- it 'selects all pipeline pipeline schedules' do
+ it 'selects all pipeline schedules' do
expect(subject.count).to be(2)
expect(subject).to include(active_schedule, inactive_schedule)
end
diff --git a/spec/fixtures/emails/paragraphs.eml b/spec/fixtures/emails/paragraphs.eml
index 2d5b5283f7e..6ab319fa83a 100644
--- a/spec/fixtures/emails/paragraphs.eml
+++ b/spec/fixtures/emails/paragraphs.eml
@@ -17,7 +17,7 @@ X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
13 Jun 2013 14:03:48 -0700 (PDT)
X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
-Is there any reason the *old* candy can't be be kept in silos while the new candy
+Is there any reason the *old* candy can't be kept in silos while the new candy
is imported into *new* silos?
The thing about candy is it stays delicious for a long time -- we can just keep
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
new file mode 100644
index 00000000000..ca90673521c
--- /dev/null
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Resolvers::IssuesResolver do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:issue) { create(:issue, project: project) }
+ set(:issue2) { create(:issue, project: project, title: 'foo') }
+
+ before do
+ project.add_developer(current_user)
+ end
+
+ describe '#resolve' do
+ it 'finds all issues' do
+ expect(resolve_issues).to contain_exactly(issue, issue2)
+ end
+
+ it 'searches issues' do
+ expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
+ end
+
+ it 'sort issues' do
+ expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue]
+ end
+
+ it 'returns issues user can see' do
+ project.add_guest(current_user)
+
+ create(:issue, confidential: true)
+
+ expect(resolve_issues).to contain_exactly(issue, issue2)
+ end
+ end
+
+ def resolve_issues(args = {}, context = { current_user: current_user })
+ resolve(described_class, obj: project, args: args, ctx: context)
+ end
+end
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
new file mode 100644
index 00000000000..63a07647a60
--- /dev/null
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+describe GitlabSchema.types['Issue'] do
+ it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Issue) }
+
+ it { expect(described_class.graphql_name).to eq('Issue') }
+end
diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb
new file mode 100644
index 00000000000..c3f84629aa2
--- /dev/null
+++ b/spec/graphql/types/permission_types/issue_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe Types::PermissionTypes::Issue do
+ it do
+ expected_permissions = [
+ :read_issue, :admin_issue, :update_issue,
+ :create_note, :reopen_issue
+ ]
+
+ expect(described_class).to have_graphql_fields(expected_permissions)
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 49606c397b9..61d4c42665a 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -14,5 +14,9 @@ describe GitlabSchema.types['Project'] do
end
end
+ describe 'nested issues' do
+ it { expect(described_class).to have_graphql_field(:issues) }
+ end
+
it { is_expected.to have_graphql_field(:pipelines) }
end
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index ab4566e261b..4a62e696cd9 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -5,6 +5,16 @@ describe TreeHelper do
let(:repository) { project.repository }
let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' }
+ def create_file(filename)
+ project.repository.create_file(
+ project.creator,
+ filename,
+ 'test this',
+ message: "Automatically created file #{filename}",
+ branch_name: 'master'
+ )
+ end
+
describe '.render_tree' do
before do
@id = sha
@@ -57,6 +67,15 @@ describe TreeHelper do
expect(fast_path).to start_with('/gitlab/root')
end
+
+ it 'encodes files starting with #' do
+ filename = '#test-file'
+ create_file(filename)
+
+ fast_path = fast_project_blob_path(project, filename)
+
+ expect(fast_path).to end_with('%23test-file')
+ end
end
describe '.fast_project_tree_path' do
@@ -73,6 +92,15 @@ describe TreeHelper do
expect(fast_path).to start_with('/gitlab/root')
end
+
+ it 'encodes files starting with #' do
+ filename = '#test-file'
+ create_file(filename)
+
+ fast_path = fast_project_tree_path(project, filename)
+
+ expect(fast_path).to end_with('%23test-file')
+ end
end
describe 'flatten_tree' do
diff --git a/spec/initializers/attr_encrypted_no_db_connection_spec.rb b/spec/initializers/attr_encrypted_no_db_connection_spec.rb
new file mode 100644
index 00000000000..2da9f1cbd96
--- /dev/null
+++ b/spec/initializers/attr_encrypted_no_db_connection_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe 'GitLab monkey-patches to AttrEncrypted' do
+ describe '#attribute_instance_methods_as_symbols_available?' do
+ it 'returns false' do
+ expect(ActiveRecord::Base.__send__(:attribute_instance_methods_as_symbols_available?)).to be_falsy
+ end
+
+ it 'does not define virtual attributes' do
+ klass = Class.new(ActiveRecord::Base) do
+ # We need some sort of table to work on
+ self.table_name = 'projects'
+
+ attr_encrypted :foo
+ end
+
+ instance = klass.new
+
+ aggregate_failures do
+ %w[
+ encrypted_foo encrypted_foo=
+ encrypted_foo_iv encrypted_foo_iv=
+ encrypted_foo_salt encrypted_foo_salt=
+ ].each do |method_name|
+ expect(instance).not_to respond_to(method_name)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index c3dfd7bedbe..6366be30079 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -123,7 +123,7 @@ describe 'create_tokens' do
create_tokens
end
- it 'sets the the keys to the values from the environment and secrets.yml' do
+ it 'sets the keys to the values from the environment and secrets.yml' do
create_tokens
expect(secrets.secret_key_base).to eq('secret_key_base')
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 091edf13cfe..7de38913bae 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -123,7 +123,7 @@ describe('Api', () => {
});
});
- describe('mergerequest', () => {
+ describe('projectMergeRequest', () => {
it('fetches a merge request', done => {
const projectPath = 'abc';
const mergeRequestId = '123456';
@@ -132,7 +132,7 @@ describe('Api', () => {
title: 'test',
});
- Api.mergeRequest(projectPath, mergeRequestId)
+ Api.projectMergeRequest(projectPath, mergeRequestId)
.then(({ data }) => {
expect(data.title).toBe('test');
})
@@ -141,7 +141,7 @@ describe('Api', () => {
});
});
- describe('mergerequest changes', () => {
+ describe('projectMergeRequestChanges', () => {
it('fetches the changes of a merge request', done => {
const projectPath = 'abc';
const mergeRequestId = '123456';
@@ -150,7 +150,7 @@ describe('Api', () => {
title: 'test',
});
- Api.mergeRequestChanges(projectPath, mergeRequestId)
+ Api.projectMergeRequestChanges(projectPath, mergeRequestId)
.then(({ data }) => {
expect(data.title).toBe('test');
})
@@ -159,7 +159,7 @@ describe('Api', () => {
});
});
- describe('mergerequest versions', () => {
+ describe('projectMergeRequestVersions', () => {
it('fetches the versions of a merge request', done => {
const projectPath = 'abc';
const mergeRequestId = '123456';
@@ -170,7 +170,7 @@ describe('Api', () => {
},
]);
- Api.mergeRequestVersions(projectPath, mergeRequestId)
+ Api.projectMergeRequestVersions(projectPath, mergeRequestId)
.then(({ data }) => {
expect(data.length).toBe(1);
expect(data[0].id).toBe(123);
diff --git a/spec/javascripts/blob_edit/blob_bundle_spec.js b/spec/javascripts/blob_edit/blob_bundle_spec.js
new file mode 100644
index 00000000000..759d170af77
--- /dev/null
+++ b/spec/javascripts/blob_edit/blob_bundle_spec.js
@@ -0,0 +1,30 @@
+import blobBundle from '~/blob_edit/blob_bundle';
+import $ from 'jquery';
+
+window.ace = {
+ config: {
+ set: () => {},
+ loadModule: () => {},
+ },
+ edit: () => ({ focus: () => {} }),
+};
+
+describe('EditBlob', () => {
+ beforeEach(() => {
+ setFixtures(`
+ <div class="js-edit-blob-form">
+ <button class="js-commit-button"></button>
+ </div>`);
+ blobBundle();
+ });
+
+ it('sets the window beforeunload listener to a function returning a string', () => {
+ expect(window.onbeforeunload()).toBe('');
+ });
+
+ it('removes beforeunload listener if commit button is clicked', () => {
+ $('.js-commit-button').click();
+
+ expect(window.onbeforeunload).toBeNull();
+ });
+});
diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js
index 0e2cc13fa52..928bf70f3a2 100644
--- a/spec/javascripts/clusters/components/applications_spec.js
+++ b/spec/javascripts/clusters/components/applications_spec.js
@@ -20,6 +20,7 @@ describe('Applications', () => {
applications: {
helm: { title: 'Helm Tiller' },
ingress: { title: 'Ingress' },
+ cert_manager: { title: 'Cert-Manager' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub' },
@@ -36,6 +37,10 @@ describe('Applications', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).toBeDefined();
});
+ it('renders a row for Cert-Manager', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).toBeDefined();
+ });
+
it('renders a row for Prometheus', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeDefined();
});
@@ -65,6 +70,7 @@ describe('Applications', () => {
externalIp: '0.0.0.0',
},
helm: { title: 'Helm Tiller' },
+ cert_manager: { title: 'Cert-Manager' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '' },
@@ -89,6 +95,7 @@ describe('Applications', () => {
status: 'installed',
},
helm: { title: 'Helm Tiller' },
+ cert_manager: { title: 'Cert-Manager' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '' },
@@ -109,6 +116,7 @@ describe('Applications', () => {
applications: {
helm: { title: 'Helm Tiller' },
ingress: { title: 'Ingress' },
+ cert_manager: { title: 'Cert-Manager' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '' },
@@ -128,6 +136,7 @@ describe('Applications', () => {
applications: {
helm: { title: 'Helm Tiller', status: 'installed' },
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
+ cert_manager: { title: 'Cert-Manager' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
@@ -145,6 +154,7 @@ describe('Applications', () => {
applications: {
helm: { title: 'Helm Tiller', status: 'installed' },
ingress: { title: 'Ingress', status: 'installed' },
+ cert_manager: { title: 'Cert-Manager' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
@@ -162,6 +172,7 @@ describe('Applications', () => {
applications: {
helm: { title: 'Helm Tiller', status: 'installed' },
ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
+ cert_manager: { title: 'Cert-Manager' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' },
@@ -179,6 +190,7 @@ describe('Applications', () => {
applications: {
helm: { title: 'Helm Tiller' },
ingress: { title: 'Ingress' },
+ cert_manager: { title: 'Cert-Manager' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
jupyter: { title: 'JupyterHub', status: 'not_installable' },
diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js
index 73abf6504c0..540d7f30858 100644
--- a/spec/javascripts/clusters/services/mock_data.js
+++ b/spec/javascripts/clusters/services/mock_data.js
@@ -38,6 +38,11 @@ const CLUSTERS_MOCK_DATA = {
status: APPLICATION_STATUS.INSTALLING,
status_reason: 'Cannot connect',
},
+ {
+ name: 'cert_manager',
+ status: APPLICATION_STATUS.ERROR,
+ status_reason: 'Cannot connect',
+ },
],
},
},
@@ -77,6 +82,11 @@ const CLUSTERS_MOCK_DATA = {
status: APPLICATION_STATUS.INSTALLABLE,
status_reason: 'Cannot connect',
},
+ {
+ name: 'cert_manager',
+ status: APPLICATION_STATUS.ERROR,
+ status_reason: 'Cannot connect',
+ },
],
},
},
@@ -84,6 +94,7 @@ const CLUSTERS_MOCK_DATA = {
POST: {
'/gitlab-org/gitlab-shell/clusters/1/applications/helm': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {},
+ '/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/runner': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {},
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index 34ed36afa5b..7ea0878ad45 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -107,6 +107,14 @@ describe('Clusters Store', () => {
requestStatus: null,
requestReason: null,
hostname: null,
+ externalIp: null,
+ },
+ cert_manager: {
+ title: 'Cert-Manager',
+ status: mockResponseData.applications[6].status,
+ statusReason: mockResponseData.applications[6].status_reason,
+ requestStatus: null,
+ requestReason: null,
},
},
});
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index 3c9b5ee0176..1a0b7612ee9 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -3,7 +3,6 @@ import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper
import { TEST_HOST } from 'spec/test_constants';
import App from '~/diffs/components/app.vue';
import createDiffsStore from '../create_diffs_store';
-import getDiffWithCommit from '../mock_data/diff_with_commit';
describe('diffs/components/app', () => {
const oldMrTabs = window.mrTabs;
@@ -40,43 +39,4 @@ describe('diffs/components/app', () => {
it('does not show commit info', () => {
expect(vm.$el).not.toContainElement('.blob-commit-info');
});
-
- it('shows comments message, with commit', done => {
- vm.$store.state.diffs.commit = getDiffWithCommit().commit;
-
- vm.$nextTick()
- .then(() => {
- expect(vm.$el).toContainText('Only comments from the following commit are shown below');
- expect(vm.$el).toContainElement('.blob-commit-info');
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('shows comments message, with old mergeRequestDiff', done => {
- vm.$store.state.diffs.mergeRequestDiff = { latest: false };
- vm.$store.state.diffs.targetBranch = 'master';
-
- vm.$nextTick()
- .then(() => {
- expect(vm.$el).toContainText(
- "Not all comments are displayed because you're viewing an old version of the diff.",
- );
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('shows comments message, with startVersion', done => {
- vm.$store.state.diffs.startVersion = 'test';
-
- vm.$nextTick()
- .then(() => {
- expect(vm.$el).toContainText(
- "Not all comments are displayed because you're comparing two versions of the diff.",
- );
- })
- .then(done)
- .catch(done.fail);
- });
});
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index d9d7f61785f..75c66e9ca82 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -3,6 +3,7 @@ import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
import store from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffsMockData from '../mock_data/merge_request_diffs';
+import getDiffWithCommit from '../mock_data/diff_with_commit';
describe('CompareVersions', () => {
let vm;
@@ -122,4 +123,24 @@ describe('CompareVersions', () => {
expect(vm.isWhitespaceVisible()).toBe(true);
});
});
+
+ describe('commit', () => {
+ beforeEach(done => {
+ vm.$store.state.diffs.commit = getDiffWithCommit().commit;
+ vm.mergeRequestDiffs = [];
+
+ vm.$nextTick(done);
+ });
+
+ it('renders latest version button', () => {
+ expect(vm.$el.querySelector('.js-latest-version').textContent.trim()).toBe(
+ 'Show latest version',
+ );
+ });
+
+ it('renders short commit ID', () => {
+ expect(vm.$el.textContent).toContain('Viewing commit');
+ expect(vm.$el.textContent).toContain(vm.commit.short_id);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index 9530b50c729..b77907ff26f 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -464,7 +464,11 @@ describe('diff_file_header', () => {
propsCopy.addMergeRequestButtons = true;
propsCopy.diffFile.deleted_file = true;
- const discussionGetter = () => [diffDiscussionMock];
+ const discussionGetter = () => [
+ {
+ ...diffDiscussionMock,
+ },
+ ];
const notesModuleMock = notesModule();
notesModuleMock.getters.discussions = discussionGetter;
vm = mountComponentWithStore(Component, {
diff --git a/spec/javascripts/diffs/components/diff_line_note_form_spec.js b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
index 81b66cf7c9b..b983dc35a57 100644
--- a/spec/javascripts/diffs/components/diff_line_note_form_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
@@ -62,6 +62,7 @@ describe('DiffLineNoteForm', () => {
component.$nextTick(() => {
expect(component.cancelCommentForm).toHaveBeenCalledWith({
lineCode: diffLines[0].line_code,
+ fileHash: component.diffFileHash,
});
expect(component.resetAutoSave).toHaveBeenCalled();
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index acd95a3dd8b..43d8d950bed 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -310,13 +310,13 @@ describe('DiffsStoreActions', () => {
describe('showCommentForm', () => {
it('should call mutation to show comment form', done => {
- const payload = { lineCode: 'lineCode' };
+ const payload = { lineCode: 'lineCode', fileHash: 'hash' };
testAction(
showCommentForm,
payload,
{},
- [{ type: types.ADD_COMMENT_FORM_LINE, payload }],
+ [{ type: types.TOGGLE_LINE_HAS_FORM, payload: { ...payload, hasForm: true } }],
[],
done,
);
@@ -325,13 +325,13 @@ describe('DiffsStoreActions', () => {
describe('cancelCommentForm', () => {
it('should call mutation to cancel comment form', done => {
- const payload = { lineCode: 'lineCode' };
+ const payload = { lineCode: 'lineCode', fileHash: 'hash' };
testAction(
cancelCommentForm,
payload,
{},
- [{ type: types.REMOVE_COMMENT_FORM_LINE, payload }],
+ [{ type: types.TOGGLE_LINE_HAS_FORM, payload: { ...payload, hasForm: false } }],
[],
done,
);
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index eef95c823fb..582535e0a53 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -186,77 +186,6 @@ describe('Diffs Module Getters', () => {
});
});
- describe('shouldRenderParallelCommentRow', () => {
- let line;
-
- beforeEach(() => {
- line = {};
-
- discussionMock.expanded = true;
-
- line.left = {
- line_code: 'ABC',
- discussions: [discussionMock],
- };
-
- line.right = {
- line_code: 'DEF',
- discussions: [discussionMock1],
- };
- });
-
- it('returns true when discussion is expanded', () => {
- expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true);
- });
-
- it('returns false when no discussion was found', () => {
- line.left.discussions = [];
- line.right.discussions = [];
-
- localState.diffLineCommentForms.ABC = false;
- localState.diffLineCommentForms.DEF = false;
-
- expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(false);
- });
-
- it('returns true when discussionForm was found', () => {
- localState.diffLineCommentForms.ABC = {};
-
- expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true);
- });
- });
-
- describe('shouldRenderInlineCommentRow', () => {
- let line;
-
- beforeEach(() => {
- discussionMock.expanded = true;
-
- line = {
- lineCode: 'ABC',
- discussions: [discussionMock],
- };
- });
-
- it('returns true when diffLineCommentForms has form', () => {
- localState.diffLineCommentForms.ABC = {};
-
- expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true);
- });
-
- it('returns false when no line discussions were found', () => {
- line.discussions = [];
-
- expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(false);
- });
-
- it('returns true if all found discussions are expanded', () => {
- discussionMock.expanded = true;
-
- expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true);
- });
- });
-
describe('getDiffFileDiscussions', () => {
it('returns an array with discussions when fileHash matches and the discussion belongs to a diff', () => {
discussionMock.diff_file.file_hash = diffFileMock.file_hash;
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 598d723c940..d1040ace5ca 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -55,32 +55,6 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('ADD_COMMENT_FORM_LINE', () => {
- it('should set a truthy reference for the given line code in diffLineCommentForms', () => {
- const state = { diffLineCommentForms: {} };
- const lineCode = 'FDE';
-
- mutations[types.ADD_COMMENT_FORM_LINE](state, { lineCode });
-
- expect(state.diffLineCommentForms[lineCode]).toBeTruthy();
- });
- });
-
- describe('REMOVE_COMMENT_FORM_LINE', () => {
- it('should remove given reference from diffLineCommentForms', () => {
- const state = { diffLineCommentForms: {} };
- const lineCode = 'FDE';
-
- mutations[types.ADD_COMMENT_FORM_LINE](state, { lineCode });
-
- expect(state.diffLineCommentForms[lineCode]).toBeTruthy();
-
- mutations[types.REMOVE_COMMENT_FORM_LINE](state, { lineCode });
-
- expect(state.diffLineCommentForms[lineCode]).toBeUndefined();
- });
- });
-
describe('EXPAND_ALL_FILES', () => {
it('should change the collapsed prop from diffFiles', () => {
const diffFile = {
@@ -98,7 +72,9 @@ describe('DiffsStoreMutations', () => {
it('should call utils.addContextLines with proper params', () => {
const options = {
lineNumbers: { oldLineNumber: 1, newLineNumber: 2 },
- contextLines: [{ old_line: 1, new_line: 1, line_code: 'ff9200_1_1', discussions: [] }],
+ contextLines: [
+ { old_line: 1, new_line: 1, line_code: 'ff9200_1_1', discussions: [], hasForm: false },
+ ],
fileHash: 'ff9200',
params: {
bottom: true,
@@ -383,4 +359,35 @@ describe('DiffsStoreMutations', () => {
expect(state.currentDiffFileId).toBe('somefileid');
});
});
+
+ describe('TOGGLE_LINE_HAS_FORM', () => {
+ it('sets hasForm on lines', () => {
+ const file = {
+ file_hash: 'hash',
+ parallel_diff_lines: [
+ { left: { line_code: '123', hasForm: false }, right: {} },
+ { left: {}, right: { line_code: '124', hasForm: false } },
+ ],
+ highlighted_diff_lines: [
+ { line_code: '123', hasForm: false },
+ { line_code: '124', hasForm: false },
+ ],
+ };
+ const state = {
+ diffFiles: [file],
+ };
+
+ mutations[types.TOGGLE_LINE_HAS_FORM](state, {
+ lineCode: '123',
+ hasForm: true,
+ fileHash: 'hash',
+ });
+
+ expect(file.highlighted_diff_lines[0].hasForm).toBe(true);
+ expect(file.highlighted_diff_lines[1].hasForm).toBe(false);
+
+ expect(file.parallel_diff_lines[0].left.hasForm).toBe(true);
+ expect(file.parallel_diff_lines[1].right.hasForm).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/helpers/scroll_into_view_promise.js b/spec/javascripts/helpers/scroll_into_view_promise.js
new file mode 100644
index 00000000000..0edea2103da
--- /dev/null
+++ b/spec/javascripts/helpers/scroll_into_view_promise.js
@@ -0,0 +1,28 @@
+export default function scrollIntoViewPromise(intersectionTarget, timeout = 100, maxTries = 5) {
+ return new Promise((resolve, reject) => {
+ let intersectionObserver;
+ let retry = 0;
+
+ const intervalId = setInterval(() => {
+ if (retry >= maxTries) {
+ intersectionObserver.disconnect();
+ clearInterval(intervalId);
+ reject(new Error(`Could not scroll target into viewPort within ${timeout * maxTries} ms`));
+ }
+ retry += 1;
+ intersectionTarget.scrollIntoView();
+ }, timeout);
+
+ intersectionObserver = new IntersectionObserver(entries => {
+ if (entries[0].isIntersecting) {
+ intersectionObserver.disconnect();
+ clearInterval(intervalId);
+ resolve();
+ }
+ });
+
+ intersectionObserver.observe(intersectionTarget);
+
+ intersectionTarget.scrollIntoView();
+ });
+}
diff --git a/spec/javascripts/helpers/wait_for_attribute_change.js b/spec/javascripts/helpers/wait_for_attribute_change.js
new file mode 100644
index 00000000000..8f22d569222
--- /dev/null
+++ b/spec/javascripts/helpers/wait_for_attribute_change.js
@@ -0,0 +1,16 @@
+export default (domElement, attributes, timeout = 1500) =>
+ new Promise((resolve, reject) => {
+ let observer;
+ const timeoutId = setTimeout(() => {
+ observer.disconnect();
+ reject(new Error(`Could not see an attribute update within ${timeout} ms`));
+ }, timeout);
+
+ observer = new MutationObserver(() => {
+ clearTimeout(timeoutId);
+ observer.disconnect();
+ resolve();
+ });
+
+ observer.observe(domElement, { attributes: true, attributeFilter: attributes });
+ });
diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js
index c02a1ad246c..55f40be0e4e 100644
--- a/spec/javascripts/ide/components/ide_spec.js
+++ b/spec/javascripts/ide/components/ide_spec.js
@@ -29,7 +29,7 @@ describe('ide component', () => {
resetStore(vm.$store);
});
- it('does not render right right when no files open', () => {
+ it('does not render right when no files open', () => {
expect(vm.$el.querySelector('.panel-right')).toBeNull();
});
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index 1ca811e996b..7ddc734ff56 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -296,7 +296,7 @@ describe('IDE store file actions', () => {
.getFileData({ state: store.state, commit() {}, dispatch }, { path: localFile.path })
.then(() => {
expect(dispatch).toHaveBeenCalledWith('setErrorMessage', {
- text: 'An error occured whilst loading the file.',
+ text: 'An error occurred whilst loading the file.',
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
@@ -408,7 +408,7 @@ describe('IDE store file actions', () => {
.then(done.fail)
.catch(() => {
expect(dispatch).toHaveBeenCalledWith('setErrorMessage', {
- text: 'An error occured whilst loading the file content.',
+ text: 'An error occurred whilst loading the file content.',
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js
index d8e9260e932..9bfc7c397b8 100644
--- a/spec/javascripts/ide/stores/actions/merge_request_spec.js
+++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js
@@ -82,7 +82,7 @@ describe('IDE store merge request actions', () => {
.then(done.fail)
.catch(() => {
expect(dispatch).toHaveBeenCalledWith('setErrorMessage', {
- text: 'An error occured whilst loading the merge request.',
+ text: 'An error occurred whilst loading the merge request.',
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
@@ -155,7 +155,7 @@ describe('IDE store merge request actions', () => {
.then(done.fail)
.catch(() => {
expect(dispatch).toHaveBeenCalledWith('setErrorMessage', {
- text: 'An error occured whilst loading the merge request changes.',
+ text: 'An error occurred whilst loading the merge request changes.',
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
@@ -225,7 +225,7 @@ describe('IDE store merge request actions', () => {
.then(done.fail)
.catch(() => {
expect(dispatch).toHaveBeenCalledWith('setErrorMessage', {
- text: 'An error occured whilst loading the merge request version data.',
+ text: 'An error occurred whilst loading the merge request version data.',
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js
index d47c60dc581..bd41e87bf0e 100644
--- a/spec/javascripts/ide/stores/actions/tree_spec.js
+++ b/spec/javascripts/ide/stores/actions/tree_spec.js
@@ -143,7 +143,7 @@ describe('Multi-file store tree actions', () => {
.then(done.fail)
.catch(() => {
expect(dispatch).toHaveBeenCalledWith('setErrorMessage', {
- text: 'An error occured whilst loading all the files.',
+ text: 'An error occurred whilst loading all the files.',
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: { projectId: 'abc/def', branchId: 'master-testing' },
diff --git a/spec/javascripts/ide/stores/modules/branches/actions_spec.js b/spec/javascripts/ide/stores/modules/branches/actions_spec.js
index 2b3eac282f6..9c61ba3d1a6 100644
--- a/spec/javascripts/ide/stores/modules/branches/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/branches/actions_spec.js
@@ -59,7 +59,7 @@ describe('IDE branches actions', () => {
});
describe('receiveBranchesError', () => {
- it('should should commit error', done => {
+ it('should commit error', done => {
testAction(
receiveBranchesError,
{ search: TEST_SEARCH },
diff --git a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
index 62699143a91..9e2ba1f5ce9 100644
--- a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
@@ -14,10 +14,14 @@ import testAction from '../../../../helpers/vuex_action_helper';
describe('IDE merge requests actions', () => {
let mockedState;
+ let mockedRootState;
let mock;
beforeEach(() => {
mockedState = state();
+ mockedRootState = {
+ currentProjectId: 7,
+ };
mock = new MockAdapter(axios);
});
@@ -39,7 +43,7 @@ describe('IDE merge requests actions', () => {
});
describe('receiveMergeRequestsError', () => {
- it('should should commit error', done => {
+ it('should commit error', done => {
testAction(
receiveMergeRequestsError,
{ type: 'created', search: '' },
@@ -86,13 +90,16 @@ describe('IDE merge requests actions', () => {
describe('success', () => {
beforeEach(() => {
- mock.onGet(/\/api\/v4\/merge_requests(.*)$/).replyOnce(200, mergeRequests);
+ mock.onGet(/\/api\/v4\/merge_requests\/?/).replyOnce(200, mergeRequests);
});
it('calls API with params', () => {
const apiSpy = spyOn(axios, 'get').and.callThrough();
- fetchMergeRequests({ dispatch() {}, state: mockedState }, { type: 'created' });
+ fetchMergeRequests(
+ { dispatch() {}, state: mockedState, rootState: mockedRootState },
+ { type: 'created' },
+ );
expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), {
params: {
@@ -107,7 +114,7 @@ describe('IDE merge requests actions', () => {
const apiSpy = spyOn(axios, 'get').and.callThrough();
fetchMergeRequests(
- { dispatch() {}, state: mockedState },
+ { dispatch() {}, state: mockedState, rootState: mockedRootState },
{ type: 'created', search: 'testing search' },
);
@@ -139,6 +146,49 @@ describe('IDE merge requests actions', () => {
});
});
+ describe('success without type', () => {
+ beforeEach(() => {
+ mock.onGet(/\/api\/v4\/projects\/.+\/merge_requests\/?$/).replyOnce(200, mergeRequests);
+ });
+
+ it('calls API with project', () => {
+ const apiSpy = spyOn(axios, 'get').and.callThrough();
+
+ fetchMergeRequests(
+ { dispatch() {}, state: mockedState, rootState: mockedRootState },
+ { type: null, search: 'testing search' },
+ );
+
+ expect(apiSpy).toHaveBeenCalledWith(
+ jasmine.stringMatching(`projects/${mockedRootState.currentProjectId}/merge_requests`),
+ {
+ params: {
+ state: 'opened',
+ search: 'testing search',
+ },
+ },
+ );
+ });
+
+ it('dispatches success with received data', done => {
+ testAction(
+ fetchMergeRequests,
+ { type: null },
+ { ...mockedState, ...mockedRootState },
+ [],
+ [
+ { type: 'requestMergeRequests' },
+ { type: 'resetMergeRequests' },
+ {
+ type: 'receiveMergeRequestsSuccess',
+ payload: mergeRequests,
+ },
+ ],
+ done,
+ );
+ });
+ });
+
describe('error', () => {
beforeEach(() => {
mock.onGet(/\/api\/v4\/merge_requests(.*)$/).replyOnce(500);
diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
index c9c09ee9afe..0937ee38390 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
@@ -77,7 +77,7 @@ describe('IDE pipelines actions', () => {
{
type: 'setErrorMessage',
payload: {
- text: 'An error occured whilst fetching the latest pipeline.',
+ text: 'An error occurred whilst fetching the latest pipeline.',
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: null,
@@ -223,7 +223,7 @@ describe('IDE pipelines actions', () => {
{
type: 'setErrorMessage',
payload: {
- text: 'An error occured whilst loading the pipelines jobs.',
+ text: 'An error occurred whilst loading the pipelines jobs.',
action: jasmine.anything(),
actionText: 'Please try again',
actionPayload: { id: 1 },
@@ -360,7 +360,7 @@ describe('IDE pipelines actions', () => {
{
type: 'setErrorMessage',
payload: {
- text: 'An error occured whilst fetching the job trace.',
+ text: 'An error occurred whilst fetching the job trace.',
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: null,
diff --git a/spec/javascripts/issuable_suggestions/components/app_spec.js b/spec/javascripts/issuable_suggestions/components/app_spec.js
new file mode 100644
index 00000000000..7bb8e26b81a
--- /dev/null
+++ b/spec/javascripts/issuable_suggestions/components/app_spec.js
@@ -0,0 +1,96 @@
+import { shallowMount } from '@vue/test-utils';
+import App from '~/issuable_suggestions/components/app.vue';
+import Suggestion from '~/issuable_suggestions/components/item.vue';
+
+describe('Issuable suggestions app component', () => {
+ let vm;
+
+ function createComponent(search = 'search') {
+ vm = shallowMount(App, {
+ propsData: {
+ search,
+ projectPath: 'project',
+ },
+ });
+ }
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('does not render with empty search', () => {
+ createComponent('');
+
+ expect(vm.isVisible()).toBe(false);
+ });
+
+ describe('with data', () => {
+ let data;
+
+ beforeEach(() => {
+ data = { issues: [{ id: 1 }, { id: 2 }] };
+ });
+
+ it('renders component', () => {
+ createComponent();
+ vm.setData(data);
+
+ expect(vm.isEmpty()).toBe(false);
+ });
+
+ it('does not render with empty search', () => {
+ createComponent('');
+ vm.setData(data);
+
+ expect(vm.isVisible()).toBe(false);
+ });
+
+ it('does not render when loading', () => {
+ createComponent();
+ vm.setData({
+ ...data,
+ loading: 1,
+ });
+
+ expect(vm.isVisible()).toBe(false);
+ });
+
+ it('does not render with empty issues data', () => {
+ createComponent();
+ vm.setData({ issues: [] });
+
+ expect(vm.isVisible()).toBe(false);
+ });
+
+ it('renders list of issues', () => {
+ createComponent();
+ vm.setData(data);
+
+ expect(vm.findAll(Suggestion).length).toBe(2);
+ });
+
+ it('adds margin class to first item', () => {
+ createComponent();
+ vm.setData(data);
+
+ expect(
+ vm
+ .findAll('li')
+ .at(0)
+ .is('.append-bottom-default'),
+ ).toBe(true);
+ });
+
+ it('does not add margin class to last item', () => {
+ createComponent();
+ vm.setData(data);
+
+ expect(
+ vm
+ .findAll('li')
+ .at(1)
+ .is('.append-bottom-default'),
+ ).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/issuable_suggestions/components/item_spec.js b/spec/javascripts/issuable_suggestions/components/item_spec.js
new file mode 100644
index 00000000000..7bd1fe678f4
--- /dev/null
+++ b/spec/javascripts/issuable_suggestions/components/item_spec.js
@@ -0,0 +1,139 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlTooltip, GlLink } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
+import Suggestion from '~/issuable_suggestions/components/item.vue';
+import mockData from '../mock_data';
+
+describe('Issuable suggestions suggestion component', () => {
+ let vm;
+
+ function createComponent(suggestion = {}) {
+ vm = shallowMount(Suggestion, {
+ propsData: {
+ suggestion: {
+ ...mockData(),
+ ...suggestion,
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders title', () => {
+ createComponent();
+
+ expect(vm.text()).toContain('Test issue');
+ });
+
+ it('renders issue link', () => {
+ createComponent();
+
+ const link = vm.find(GlLink);
+
+ expect(link.attributes('href')).toBe(`${gl.TEST_HOST}/test/issue/1`);
+ });
+
+ it('renders IID', () => {
+ createComponent();
+
+ expect(vm.text()).toContain('#1');
+ });
+
+ describe('opened state', () => {
+ it('renders icon', () => {
+ createComponent();
+
+ const icon = vm.find(Icon);
+
+ expect(icon.props('name')).toBe('issue-open-m');
+ });
+
+ it('renders created timeago', () => {
+ createComponent({
+ closedAt: '',
+ });
+
+ const tooltip = vm.find(GlTooltip);
+
+ expect(tooltip.find('.d-block').text()).toContain('Opened');
+ expect(tooltip.text()).toContain('3 days ago');
+ });
+ });
+
+ describe('closed state', () => {
+ it('renders icon', () => {
+ createComponent({
+ state: 'closed',
+ });
+
+ const icon = vm.find(Icon);
+
+ expect(icon.props('name')).toBe('issue-close');
+ });
+
+ it('renders closed timeago', () => {
+ createComponent();
+
+ const tooltip = vm.find(GlTooltip);
+
+ expect(tooltip.find('.d-block').text()).toContain('Opened');
+ expect(tooltip.text()).toContain('1 day ago');
+ });
+ });
+
+ describe('author', () => {
+ it('renders author info', () => {
+ createComponent();
+
+ const link = vm.findAll(GlLink).at(1);
+
+ expect(link.text()).toContain('Author Name');
+ expect(link.text()).toContain('@author.username');
+ });
+
+ it('renders author image', () => {
+ createComponent();
+
+ const image = vm.find(UserAvatarImage);
+
+ expect(image.props('imgSrc')).toBe(`${gl.TEST_HOST}/avatar`);
+ });
+ });
+
+ describe('counts', () => {
+ it('renders upvotes count', () => {
+ createComponent();
+
+ const count = vm.findAll('.suggestion-counts span').at(0);
+
+ expect(count.text()).toContain('1');
+ expect(count.find(Icon).props('name')).toBe('thumb-up');
+ });
+
+ it('renders notes count', () => {
+ createComponent();
+
+ const count = vm.findAll('.suggestion-counts span').at(1);
+
+ expect(count.text()).toContain('2');
+ expect(count.find(Icon).props('name')).toBe('comment');
+ });
+ });
+
+ describe('confidential', () => {
+ it('renders confidential icon', () => {
+ createComponent({
+ confidential: true,
+ });
+
+ const icon = vm.find(Icon);
+
+ expect(icon.props('name')).toBe('eye-slash');
+ expect(icon.attributes('data-original-title')).toBe('Confidential');
+ });
+ });
+});
diff --git a/spec/javascripts/issuable_suggestions/mock_data.js b/spec/javascripts/issuable_suggestions/mock_data.js
new file mode 100644
index 00000000000..4f0f9ef8d62
--- /dev/null
+++ b/spec/javascripts/issuable_suggestions/mock_data.js
@@ -0,0 +1,26 @@
+function getDate(daysMinus) {
+ const today = new Date();
+ today.setDate(today.getDate() - daysMinus);
+
+ return today.toISOString();
+}
+
+export default () => ({
+ id: 1,
+ iid: 1,
+ state: 'opened',
+ upvotes: 1,
+ userNotesCount: 2,
+ closedAt: getDate(1),
+ createdAt: getDate(3),
+ updatedAt: getDate(2),
+ confidential: false,
+ webUrl: `${gl.TEST_HOST}/test/issue/1`,
+ title: 'Test issue',
+ author: {
+ avatarUrl: `${gl.TEST_HOST}/avatar`,
+ name: 'Author Name',
+ username: 'author.username',
+ webUrl: `${gl.TEST_HOST}/author`,
+ },
+});
diff --git a/spec/javascripts/issue_show/components/edited_spec.js b/spec/javascripts/issue_show/components/edited_spec.js
index 7f09db837bb..a1683f060c0 100644
--- a/spec/javascripts/issue_show/components/edited_spec.js
+++ b/spec/javascripts/issue_show/components/edited_spec.js
@@ -46,14 +46,4 @@ describe('edited', () => {
expect(editedComponent.$el.querySelector('.author-link')).toBeFalsy();
expect(editedComponent.$el.querySelector('time')).toBeTruthy();
});
-
- it('renders time ago tooltip at the bottom', () => {
- const editedComponent = new EditedComponent({
- propsData: {
- updatedAt: '2017-05-15T12:31:04.428Z',
- },
- }).$mount();
-
- expect(editedComponent.$el.querySelector('time').dataset.placement).toEqual('bottom');
- });
});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index fcf3780f0ea..ba5d672f189 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -160,9 +160,7 @@ describe('Job App ', () => {
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(
- "This job is stuck, because you don't have any active runners that can run this job.",
- );
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner')).not.toBeNull();
done();
}, 0);
});
@@ -195,9 +193,7 @@ describe('Job App ', () => {
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(
- "This job is stuck, because you don't have any active runners online with any of these tags assigned to them:",
- );
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
done();
}, 0);
});
@@ -230,9 +226,7 @@ describe('Job App ', () => {
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(
- "This job is stuck, because you don't have any active runners online with any of these tags assigned to them:",
- );
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
done();
}, 0);
});
diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js
index eac4756e8a9..cbdc1644430 100644
--- a/spec/javascripts/lazy_loader_spec.js
+++ b/spec/javascripts/lazy_loader_spec.js
@@ -1,16 +1,19 @@
import LazyLoader from '~/lazy_loader';
import { TEST_HOST } from './test_constants';
-
-let lazyLoader = null;
+import scrollIntoViewPromise from './helpers/scroll_into_view_promise';
+import waitForPromises from './helpers/wait_for_promises';
+import waitForAttributeChange from './helpers/wait_for_attribute_change';
const execImmediately = callback => {
callback();
};
describe('LazyLoader', function() {
+ let lazyLoader = null;
+
preloadFixtures('issues/issue_with_comment.html.raw');
- describe('with IntersectionObserver disabled', () => {
+ describe('without IntersectionObserver', () => {
beforeEach(function() {
loadFixtures('issues/issue_with_comment.html.raw');
@@ -36,14 +39,15 @@ describe('LazyLoader', function() {
it('should copy value from data-src to src for img 1', function(done) {
const img = document.querySelectorAll('img[data-src]')[0];
const originalDataSrc = img.getAttribute('data-src');
- img.scrollIntoView();
-
- setTimeout(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
- expect(img.getAttribute('src')).toBe(originalDataSrc);
- expect(img).toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+
+ Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])])
+ .then(() => {
+ expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(img.getAttribute('src')).toBe(originalDataSrc);
+ expect(img).toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
it('should lazy load dynamically added data-src images', function(done) {
@@ -52,14 +56,18 @@ describe('LazyLoader', function() {
newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg);
- newImg.scrollIntoView();
-
- setTimeout(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
- expect(newImg.getAttribute('src')).toBe(testPath);
- expect(newImg).toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+
+ Promise.all([
+ scrollIntoViewPromise(newImg),
+ waitForAttributeChange(newImg, ['data-src', 'src']),
+ ])
+ .then(() => {
+ expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(newImg.getAttribute('src')).toBe(testPath);
+ expect(newImg).toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
it('should not alter normal images', function(done) {
@@ -67,13 +75,15 @@ describe('LazyLoader', function() {
const testPath = `${TEST_HOST}/img/testimg.png`;
newImg.setAttribute('src', testPath);
document.body.appendChild(newImg);
- newImg.scrollIntoView();
- setTimeout(() => {
- expect(LazyLoader.loadImage).not.toHaveBeenCalled();
- expect(newImg).not.toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+ scrollIntoViewPromise(newImg)
+ .then(waitForPromises)
+ .then(() => {
+ expect(LazyLoader.loadImage).not.toHaveBeenCalled();
+ expect(newImg).not.toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
it('should not load dynamically added pictures if content observer is turned off', done => {
@@ -84,13 +94,15 @@ describe('LazyLoader', function() {
newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg);
- newImg.scrollIntoView();
- setTimeout(() => {
- expect(LazyLoader.loadImage).not.toHaveBeenCalled();
- expect(newImg).not.toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+ scrollIntoViewPromise(newImg)
+ .then(waitForPromises)
+ .then(() => {
+ expect(LazyLoader.loadImage).not.toHaveBeenCalled();
+ expect(newImg).not.toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
it('should load dynamically added pictures if content observer is turned off and on again', done => {
@@ -102,17 +114,22 @@ describe('LazyLoader', function() {
newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg);
- newImg.scrollIntoView();
- setTimeout(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
- expect(newImg).toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+ Promise.all([
+ scrollIntoViewPromise(newImg),
+ waitForAttributeChange(newImg, ['data-src', 'src']),
+ ])
+ .then(waitForPromises)
+ .then(() => {
+ expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(newImg).toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
});
- describe('with IntersectionObserver enabled', () => {
+ describe('with IntersectionObserver', () => {
beforeEach(function() {
loadFixtures('issues/issue_with_comment.html.raw');
@@ -136,14 +153,15 @@ describe('LazyLoader', function() {
it('should copy value from data-src to src for img 1', function(done) {
const img = document.querySelectorAll('img[data-src]')[0];
const originalDataSrc = img.getAttribute('data-src');
- img.scrollIntoView();
-
- setTimeout(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
- expect(img.getAttribute('src')).toBe(originalDataSrc);
- expect(img).toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+
+ Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])])
+ .then(() => {
+ expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(img.getAttribute('src')).toBe(originalDataSrc);
+ expect(img).toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
it('should lazy load dynamically added data-src images', function(done) {
@@ -152,14 +170,18 @@ describe('LazyLoader', function() {
newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg);
- newImg.scrollIntoView();
-
- setTimeout(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
- expect(newImg.getAttribute('src')).toBe(testPath);
- expect(newImg).toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+
+ Promise.all([
+ scrollIntoViewPromise(newImg),
+ waitForAttributeChange(newImg, ['data-src', 'src']),
+ ])
+ .then(() => {
+ expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(newImg.getAttribute('src')).toBe(testPath);
+ expect(newImg).toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
it('should not alter normal images', function(done) {
@@ -167,13 +189,15 @@ describe('LazyLoader', function() {
const testPath = `${TEST_HOST}/img/testimg.png`;
newImg.setAttribute('src', testPath);
document.body.appendChild(newImg);
- newImg.scrollIntoView();
- setTimeout(() => {
- expect(LazyLoader.loadImage).not.toHaveBeenCalled();
- expect(newImg).not.toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+ scrollIntoViewPromise(newImg)
+ .then(waitForPromises)
+ .then(() => {
+ expect(LazyLoader.loadImage).not.toHaveBeenCalled();
+ expect(newImg).not.toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
it('should not load dynamically added pictures if content observer is turned off', done => {
@@ -184,13 +208,15 @@ describe('LazyLoader', function() {
newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg);
- newImg.scrollIntoView();
- setTimeout(() => {
- expect(LazyLoader.loadImage).not.toHaveBeenCalled();
- expect(newImg).not.toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+ scrollIntoViewPromise(newImg)
+ .then(waitForPromises)
+ .then(() => {
+ expect(LazyLoader.loadImage).not.toHaveBeenCalled();
+ expect(newImg).not.toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
it('should load dynamically added pictures if content observer is turned off and on again', done => {
@@ -202,13 +228,17 @@ describe('LazyLoader', function() {
newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg);
- newImg.scrollIntoView();
- setTimeout(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
- expect(newImg).toHaveClass('js-lazy-loaded');
- done();
- }, 50);
+ Promise.all([
+ scrollIntoViewPromise(newImg),
+ waitForAttributeChange(newImg, ['data-src', 'src']),
+ ])
+ .then(() => {
+ expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(newImg).toHaveClass('js-lazy-loaded');
+ done();
+ })
+ .catch(done.fail);
});
});
});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 0fb90c3b78c..1ec1e8a8dd9 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -346,6 +346,24 @@ describe('common_utils', () => {
});
});
+ describe('parseBoolean', () => {
+ it('returns true for "true"', () => {
+ expect(commonUtils.parseBoolean('true')).toEqual(true);
+ });
+
+ it('returns false for "false"', () => {
+ expect(commonUtils.parseBoolean('false')).toEqual(false);
+ });
+
+ it('returns false for "something"', () => {
+ expect(commonUtils.parseBoolean('something')).toEqual(false);
+ });
+
+ it('returns false for null', () => {
+ expect(commonUtils.parseBoolean(null)).toEqual(false);
+ });
+ });
+
describe('convertPermissionToBoolean', () => {
it('should convert a boolean in a string to a boolean', () => {
expect(commonUtils.convertPermissionToBoolean('true')).toEqual(true);
@@ -425,14 +443,16 @@ describe('common_utils', () => {
});
it('rejects the backOff promise after timing out', done => {
- commonUtils.backOff(next => next(), 64000).catch(errBackoffResp => {
- const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
+ commonUtils
+ .backOff(next => next(), 64000)
+ .catch(errBackoffResp => {
+ const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
- expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]);
- expect(errBackoffResp instanceof Error).toBe(true);
- expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT');
- done();
- });
+ expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]);
+ expect(errBackoffResp instanceof Error).toBe(true);
+ expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT');
+ done();
+ });
});
});
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 7714197c821..c8df05eccf5 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -239,4 +239,38 @@ describe('MergeRequestTabs', function() {
expect($('.content-wrapper')).toContainElement('.container-limited');
});
});
+
+ describe('tabShown', function() {
+ const mainContent = document.createElement('div');
+ const tabContent = document.createElement('div');
+
+ beforeEach(function() {
+ spyOn(mainContent, 'getBoundingClientRect').and.returnValue({ top: 10 });
+ spyOn(tabContent, 'getBoundingClientRect').and.returnValue({ top: 100 });
+ spyOn(document, 'querySelector').and.callFake(function(selector) {
+ return selector === '.content-wrapper' ? mainContent : tabContent;
+ });
+ this.class.currentAction = 'commits';
+ });
+
+ it('calls window scrollTo with options if document has scrollBehavior', function() {
+ document.documentElement.style.scrollBehavior = '';
+
+ spyOn(window, 'scrollTo');
+
+ this.class.tabShown('commits', 'foobar');
+
+ expect(window.scrollTo.calls.first().args[0]).toEqual({ top: 39, behavior: 'smooth' });
+ });
+
+ it('calls window scrollTo with two args if document does not have scrollBehavior', function() {
+ spyOnProperty(document.documentElement, 'style', 'get').and.returnValue({});
+
+ spyOn(window, 'scrollTo');
+
+ this.class.tabShown('commits', 'foobar');
+
+ expect(window.scrollTo.calls.first().args).toEqual([0, 39]);
+ });
+ });
});
diff --git a/spec/javascripts/notes/components/diff_with_note_spec.js b/spec/javascripts/notes/components/diff_with_note_spec.js
index 95461396f10..0752bd05904 100644
--- a/spec/javascripts/notes/components/diff_with_note_spec.js
+++ b/spec/javascripts/notes/components/diff_with_note_spec.js
@@ -17,7 +17,7 @@ describe('diff_with_note', () => {
};
const selectors = {
get container() {
- return vm.$refs.fileHolder;
+ return vm.$el;
},
get diffTable() {
return this.container.querySelector('.diff-content table');
@@ -70,7 +70,6 @@ describe('diff_with_note', () => {
it('shows image diff', () => {
vm = mountComponentWithStore(Component, { props, store });
- expect(selectors.container).toHaveClass('js-image-file');
expect(selectors.diffTable).not.toExist();
});
});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index 81cb3e1f74d..76e9cd03d2d 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -6,6 +6,7 @@ import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
import mockDiffFile from '../../diffs/mock_data/diff_file';
const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
+const diffDiscussionFixture = 'merge_requests/diff_discussion.json';
describe('noteable_discussion component', () => {
const Component = Vue.extend(noteableDiscussion);
@@ -79,40 +80,46 @@ describe('noteable_discussion component', () => {
});
describe('computed', () => {
- describe('hasMultipleUnresolvedDiscussions', () => {
- it('is false if there are no unresolved discussions', done => {
- spyOnProperty(vm, 'unresolvedDiscussions').and.returnValue([]);
+ describe('isRepliesCollapsed', () => {
+ it('should return false for diff discussions', done => {
+ const diffDiscussion = getJSONFixture(diffDiscussionFixture)[0];
+ vm.$store.dispatch('setInitialNotes', [diffDiscussion]);
Vue.nextTick()
.then(() => {
- expect(vm.hasMultipleUnresolvedDiscussions).toBe(false);
+ expect(vm.isRepliesCollapsed).toEqual(false);
+ expect(vm.$el.querySelector('.js-toggle-replies')).not.toBeNull();
+ expect(vm.$el.querySelector('.discussion-reply-holder')).not.toBeNull();
})
.then(done)
.catch(done.fail);
});
- it('is false if there is one unresolved discussion', done => {
- spyOnProperty(vm, 'unresolvedDiscussions').and.returnValue([discussionMock]);
-
- Vue.nextTick()
- .then(() => {
- expect(vm.hasMultipleUnresolvedDiscussions).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ it('should return false if discussion does not have a reply', () => {
+ const discussion = { ...discussionMock, resolved: true };
+ discussion.notes = discussion.notes.slice(0, 1);
+ const noRepliesVm = new Component({
+ store,
+ propsData: { discussion },
+ }).$mount();
+
+ expect(noRepliesVm.isRepliesCollapsed).toEqual(false);
+ expect(noRepliesVm.$el.querySelector('.js-toggle-replies')).toBeNull();
+ expect(vm.$el.querySelector('.discussion-reply-holder')).not.toBeNull();
+ noRepliesVm.$destroy();
});
- it('is true if there are two unresolved discussions', done => {
- const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
- discussion.notes[0].resolved = false;
- vm.$store.dispatch('setInitialNotes', [discussion, discussion]);
-
- Vue.nextTick()
- .then(() => {
- expect(vm.hasMultipleUnresolvedDiscussions).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ it('should return true for resolved non-diff discussion which has replies', () => {
+ const discussion = { ...discussionMock, resolved: true };
+ const resolvedDiscussionVm = new Component({
+ store,
+ propsData: { discussion },
+ }).$mount();
+
+ expect(resolvedDiscussionVm.isRepliesCollapsed).toEqual(true);
+ expect(resolvedDiscussionVm.$el.querySelector('.js-toggle-replies')).not.toBeNull();
+ expect(vm.$el.querySelector('.discussion-reply-holder')).not.toBeNull();
+ resolvedDiscussionVm.$destroy();
});
});
});
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index fcdd834e4a0..24c2b3e6570 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import $ from 'jquery';
import _ from 'underscore';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import * as actions from '~/notes/stores/actions';
@@ -330,10 +331,14 @@ describe('Actions Notes Store', () => {
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
+
+ $('body').attr('data-page', '');
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+
+ $('body').attr('data-page', '');
});
it('commits DELETE_NOTE and dispatches updateMergeRequestWidget', done => {
@@ -353,6 +358,39 @@ describe('Actions Notes Store', () => {
{
type: 'updateMergeRequestWidget',
},
+ {
+ type: 'updateResolvableDiscussonsCounts',
+ },
+ ],
+ done,
+ );
+ });
+
+ it('dispatches removeDiscussionsFromDiff on merge request page', done => {
+ const note = { path: `${gl.TEST_HOST}`, id: 1 };
+
+ $('body').attr('data-page', 'projects:merge_requests:show');
+
+ testAction(
+ actions.deleteNote,
+ note,
+ store.state,
+ [
+ {
+ type: 'DELETE_NOTE',
+ payload: note,
+ },
+ ],
+ [
+ {
+ type: 'updateMergeRequestWidget',
+ },
+ {
+ type: 'updateResolvableDiscussonsCounts',
+ },
+ {
+ type: 'diffs/removeDiscussionsFromDiff',
+ },
],
done,
);
@@ -399,6 +437,9 @@ describe('Actions Notes Store', () => {
{
type: 'startTaskList',
},
+ {
+ type: 'updateResolvableDiscussonsCounts',
+ },
],
done,
);
@@ -472,6 +513,9 @@ describe('Actions Notes Store', () => {
],
[
{
+ type: 'updateResolvableDiscussonsCounts',
+ },
+ {
type: 'updateMergeRequestWidget',
},
],
@@ -494,6 +538,9 @@ describe('Actions Notes Store', () => {
],
[
{
+ type: 'updateResolvableDiscussonsCounts',
+ },
+ {
type: 'updateMergeRequestWidget',
},
],
@@ -525,4 +572,17 @@ describe('Actions Notes Store', () => {
);
});
});
+
+ describe('updateResolvableDiscussonsCounts', () => {
+ it('commits UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', done => {
+ testAction(
+ actions.updateResolvableDiscussonsCounts,
+ null,
+ {},
+ [{ type: 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS' }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index f853f9ff088..c066975a43b 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -117,17 +117,15 @@ describe('Getters Notes Store', () => {
describe('allResolvableDiscussions', () => {
it('should return only resolvable discussions in same order', () => {
- const localGetters = {
- allDiscussions: [
- discussion3,
- unresolvableDiscussion,
- discussion1,
- unresolvableDiscussion,
- discussion2,
- ],
- };
+ state.discussions = [
+ discussion3,
+ unresolvableDiscussion,
+ discussion1,
+ unresolvableDiscussion,
+ discussion2,
+ ];
- expect(getters.allResolvableDiscussions(state, localGetters)).toEqual([
+ expect(getters.allResolvableDiscussions(state)).toEqual([
discussion3,
discussion1,
discussion2,
@@ -135,11 +133,9 @@ describe('Getters Notes Store', () => {
});
it('should return empty array if there are no resolvable discussions', () => {
- const localGetters = {
- allDiscussions: [unresolvableDiscussion, unresolvableDiscussion],
- };
+ state.discussions = [unresolvableDiscussion, unresolvableDiscussion];
- expect(getters.allResolvableDiscussions(state, localGetters)).toEqual([]);
+ expect(getters.allResolvableDiscussions(state)).toEqual([]);
});
});
@@ -236,7 +232,7 @@ describe('Getters Notes Store', () => {
it('should return the ID of the discussion after the ID provided', () => {
expect(getters.nextUnresolvedDiscussionId(state, localGetters)('123')).toBe('456');
expect(getters.nextUnresolvedDiscussionId(state, localGetters)('456')).toBe('789');
- expect(getters.nextUnresolvedDiscussionId(state, localGetters)('789')).toBe(undefined);
+ expect(getters.nextUnresolvedDiscussionId(state, localGetters)('789')).toBe('123');
});
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index 461de5a3106..1c4449d1055 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -437,4 +437,51 @@ describe('Notes Store mutations', () => {
expect(state.commentsDisabled).toEqual(true);
});
});
+
+ describe('UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', () => {
+ it('updates resolvableDiscussionsCount', () => {
+ const state = {
+ discussions: [
+ { individual_note: false, resolvable: true, notes: [] },
+ { individual_note: true, resolvable: true, notes: [] },
+ { individual_note: false, resolvable: false, notes: [] },
+ ],
+ resolvableDiscussionsCount: 0,
+ };
+
+ mutations.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS(state);
+
+ expect(state.resolvableDiscussionsCount).toBe(1);
+ });
+
+ it('updates unresolvedDiscussionsCount', () => {
+ const state = {
+ discussions: [
+ { individual_note: false, resolvable: true, notes: [{ resolved: false }] },
+ { individual_note: true, resolvable: true, notes: [{ resolved: false }] },
+ { individual_note: false, resolvable: false, notes: [{ resolved: false }] },
+ ],
+ unresolvedDiscussionsCount: 0,
+ };
+
+ mutations.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS(state);
+
+ expect(state.unresolvedDiscussionsCount).toBe(1);
+ });
+
+ it('updates hasUnresolvedDiscussions', () => {
+ const state = {
+ discussions: [
+ { individual_note: false, resolvable: true, notes: [{ resolved: false }] },
+ { individual_note: false, resolvable: true, notes: [{ resolved: false }] },
+ { individual_note: false, resolvable: false, notes: [{ resolved: false }] },
+ ],
+ hasUnresolvedDiscussions: 0,
+ };
+
+ mutations.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS(state);
+
+ expect(state.hasUnresolvedDiscussions).toBe(true);
+ });
+ });
});
diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
index a3b93280b4b..e91685e50c5 100644
--- a/spec/javascripts/performance_bar/components/detailed_metric_spec.js
+++ b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
@@ -67,7 +67,7 @@ describe('detailedMetric', () => {
vm.$el
.querySelectorAll('.performance-bar-modal td:nth-child(3)')
.forEach((request, index) => {
- expect(request.innerText).toContain(requestDetails[index].request);
+ expect(request.innerText).toEqual(requestDetails[index].request);
});
});
diff --git a/spec/javascripts/shared/popover_spec.js b/spec/javascripts/shared/popover_spec.js
index 85bde075b77..cc2b2014d38 100644
--- a/spec/javascripts/shared/popover_spec.js
+++ b/spec/javascripts/shared/popover_spec.js
@@ -112,8 +112,8 @@ describe('popover', () => {
length: 0,
};
- spyOn($.fn, 'init').and.callFake(
- selector => (selector === '.popover:hover' ? fakeJquery : $.fn),
+ spyOn($.fn, 'init').and.callFake(selector =>
+ selector === '.popover:hover' ? fakeJquery : $.fn,
);
spyOn(togglePopover, 'call');
mouseleave();
@@ -126,8 +126,8 @@ describe('popover', () => {
length: 1,
};
- spyOn($.fn, 'init').and.callFake(
- selector => (selector === '.popover:hover' ? fakeJquery : $.fn),
+ spyOn($.fn, 'init').and.callFake(selector =>
+ selector === '.popover:hover' ? fakeJquery : $.fn,
);
spyOn(togglePopover, 'call');
mouseleave();
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index b688a299052..52da6a79939 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -51,8 +51,8 @@ describe('SigninTabsMemoizer', () => {
const fakeTab = {
click: () => {},
};
- spyOn(document, 'querySelector').and.callFake(
- selector => (selector === `${tabSelector} a[href="#bogus"]` ? null : fakeTab),
+ spyOn(document, 'querySelector').and.callFake(selector =>
+ selector === `${tabSelector} a[href="#bogus"]` ? null : fakeTab,
);
spyOn(fakeTab, 'click');
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
index 056b4df8fdc..e355416bd27 100644
--- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -252,5 +252,33 @@ describe('Deployment component', () => {
);
});
});
+
+ describe('created', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ deployment: Object.assign({}, deploymentMockData, { status: 'created' }),
+ showMetrics: true,
+ });
+ });
+
+ it('renders information about created deployment', () => {
+ expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Will deploy to');
+ });
+ });
+
+ describe('canceled', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ deployment: Object.assign({}, deploymentMockData, { status: 'canceled' }),
+ showMetrics: true,
+ });
+ });
+
+ it('renders information about canceled deployment', () => {
+ expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain(
+ 'Failed to deploy to',
+ );
+ });
+ });
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
index 14d6e8d7556..300133dc602 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
@@ -44,7 +44,10 @@ describe('Merge request widget rebase component', () => {
.textContent.trim();
expect(text).toContain('Fast-forward merge is not possible.');
- expect(text).toContain('Rebase the source branch onto the target branch or merge target');
+ expect(text.replace(/\s\s+/g, ' ')).toContain(
+ 'Rebase the source branch onto the target branch or merge target',
+ );
+
expect(text).toContain('branch into source branch to allow this merge request to be merged.');
});
@@ -78,7 +81,7 @@ describe('Merge request widget rebase component', () => {
expect(text).toContain('Fast-forward merge is not possible.');
expect(text).toContain('Rebase the source branch onto');
expect(text).toContain('foo');
- expect(text).toContain('to allow this merge request to be merged.');
+ expect(text.replace(/\s\s+/g, ' ')).toContain('to allow this merge request to be merged.');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
index 096301837c4..5fd8093bf5c 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
@@ -33,7 +33,7 @@ describe('MRWidgetMissingBranch', () => {
expect(el.classList.contains('mr-widget-body')).toBeTruthy();
expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(content).toContain('source branch does not exist.');
+ expect(content.replace(/\s\s+/g, ' ')).toContain('source branch does not exist.');
expect(content).toContain('Please restore it or use a different source branch');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
index babb8cea0ab..bd0bd36ebc2 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
@@ -19,7 +19,9 @@ describe('NothingToMerge', () => {
"Currently there are no changes in this merge request's source branch",
);
- expect(vm.$el.innerText).toContain('Please push new commits or use a different branch.');
+ expect(vm.$el.innerText.replace(/\s\s+/g, ' ')).toContain(
+ 'Please push new commits or use a different branch.',
+ );
});
it('should not show new blob link if there is no link available', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
index 88937df2f7b..7b1d589dcf8 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
@@ -85,7 +85,9 @@ describe('Wip', () => {
expect(el.innerText).toContain('This is a Work in Progress');
expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy();
expect(el.querySelector('button').innerText).toContain('Merge');
- expect(el.querySelector('.js-remove-wip').innerText).toContain('Resolve WIP status');
+ expect(el.querySelector('.js-remove-wip').innerText.replace(/\s\s+/g, ' ')).toContain(
+ 'Resolve WIP status',
+ );
});
it('should not show removeWIP button is user cannot update MR', done => {
diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
index 745571d0a97..536bb57b946 100644
--- a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
+++ b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
@@ -26,24 +26,11 @@ describe('Time ago with tooltip component', () => {
formatDate('2017-05-08T14:57:39.781Z'),
);
- expect(vm.$el.getAttribute('data-placement')).toEqual('top');
-
const timeago = getTimeago();
expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z'));
});
- it('should render tooltip placed in bottom', () => {
- vm = new TimeagoTooltip({
- propsData: {
- time: '2017-05-08T14:57:39.781Z',
- tooltipPlacement: 'bottom',
- },
- }).$mount();
-
- expect(vm.$el.getAttribute('data-placement')).toEqual('bottom');
- });
-
it('should render provided html class', () => {
vm = new TimeagoTooltip({
propsData: {
diff --git a/spec/lib/banzai/filter/absolute_link_filter_spec.rb b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
index a3ad056efcd..50be551cd90 100644
--- a/spec/lib/banzai/filter/absolute_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
@@ -28,7 +28,7 @@ describe Banzai::Filter::AbsoluteLinkFilter do
end
context 'if relative_url_root is set' do
- it 'joins the url without without doubling the path' do
+ it 'joins the url without doubling the path' do
allow(Gitlab.config.gitlab).to receive(:url).and_return("#{fake_url}/gitlab/")
doc = filter(link("/gitlab/foo", 'gfm'), only_path_context)
expect(doc.at_css('a')['href']).to eq "#{fake_url}/gitlab/foo"
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index df24cef0b8b..91b0499375d 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -104,5 +104,17 @@ describe Banzai::Pipeline::GfmPipeline do
expect(output).to include("src=\"test%20image.png\"")
end
+
+ it 'sanitizes the fixed link' do
+ markdown_xss = "[xss](javascript: alert%28document.domain%29)"
+ output = described_class.to_html(markdown_xss, project: project)
+
+ expect(output).not_to include("javascript")
+
+ markdown_xss = "<invalidtag>\n[xss](javascript:alert%28document.domain%29)"
+ output = described_class.to_html(markdown_xss, project: project)
+
+ expect(output).not_to include("javascript")
+ end
end
end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 8947e2ac4fb..e0691aba600 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -205,28 +205,18 @@ describe ExtractsPath do
end
describe '#lfs_blob_ids' do
- shared_examples '#lfs_blob_ids' do
- let(:tag) { @project.repository.add_tag(@project.owner, 'my-annotated-tag', 'master', 'test tag') }
- let(:ref) { tag.target }
- let(:params) { { ref: ref, path: 'README.md' } }
+ let(:tag) { @project.repository.add_tag(@project.owner, 'my-annotated-tag', 'master', 'test tag') }
+ let(:ref) { tag.target }
+ let(:params) { { ref: ref, path: 'README.md' } }
- before do
- @project = create(:project, :repository)
- end
-
- it 'handles annotated tags' do
- assign_ref_vars
-
- expect(lfs_blob_ids).to eq([])
- end
+ before do
+ @project = create(:project, :repository)
end
- context 'when gitaly is enabled' do
- it_behaves_like '#lfs_blob_ids'
- end
+ it 'handles annotated tags' do
+ assign_ref_vars
- context 'when gitaly is disabled', :skip_gitaly_mock do
- it_behaves_like '#lfs_blob_ids'
+ expect(lfs_blob_ids).to eq([])
end
end
end
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 3a8667e434d..dcbd12fe190 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -498,7 +498,7 @@ describe Gitlab::Auth::OAuth::User do
end
end
- describe 'ensure backwards compatibility with with sync email from provider option' do
+ describe 'ensure backwards compatibility with sync email from provider option' do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
before do
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index 242ab4a91dd..3d979132880 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -19,17 +19,17 @@ describe Gitlab::Auth::RequestAuthenticator do
allow_any_instance_of(described_class).to receive(:find_sessionless_user).and_return(sessionless_user)
allow_any_instance_of(described_class).to receive(:find_user_from_warden).and_return(session_user)
- expect(subject.user).to eq sessionless_user
+ expect(subject.user([:api])).to eq sessionless_user
end
it 'returns session user if no sessionless user found' do
allow_any_instance_of(described_class).to receive(:find_user_from_warden).and_return(session_user)
- expect(subject.user).to eq session_user
+ expect(subject.user([:api])).to eq session_user
end
it 'returns nil if no user found' do
- expect(subject.user).to be_blank
+ expect(subject.user([:api])).to be_blank
end
it 'bubbles up exceptions' do
@@ -42,26 +42,26 @@ describe Gitlab::Auth::RequestAuthenticator do
let!(:feed_token_user) { build(:user) }
it 'returns access_token user first' do
- allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_return(access_token_user)
+ allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_return(access_token_user)
allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
- expect(subject.find_sessionless_user).to eq access_token_user
+ expect(subject.find_sessionless_user([:api])).to eq access_token_user
end
it 'returns feed_token user if no access_token user found' do
allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
- expect(subject.find_sessionless_user).to eq feed_token_user
+ expect(subject.find_sessionless_user([:api])).to eq feed_token_user
end
it 'returns nil if no user found' do
- expect(subject.find_sessionless_user).to be_blank
+ expect(subject.find_sessionless_user([:api])).to be_blank
end
it 'rescue Gitlab::Auth::AuthenticationError exceptions' do
- allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_raise(Gitlab::Auth::UnauthorizedError)
+ allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_raise(Gitlab::Auth::UnauthorizedError)
- expect(subject.find_sessionless_user).to be_blank
+ expect(subject.find_sessionless_user([:api])).to be_blank
end
end
end
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 454ad1589b9..4e4c8b215c2 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Auth::UserAuthFinders do
'rack.input' => ''
}
end
- let(:request) { Rack::Request.new(env)}
+ let(:request) { Rack::Request.new(env) }
def set_param(key, value)
request.update_param(key, value)
@@ -49,6 +49,7 @@ describe Gitlab::Auth::UserAuthFinders do
describe '#find_user_from_feed_token' do
context 'when the request format is atom' do
before do
+ env['SCRIPT_NAME'] = 'url.atom'
env['HTTP_ACCEPT'] = 'application/atom+xml'
end
@@ -56,17 +57,17 @@ describe Gitlab::Auth::UserAuthFinders do
it 'returns user if valid feed_token' do
set_param(:feed_token, user.feed_token)
- expect(find_user_from_feed_token).to eq user
+ expect(find_user_from_feed_token(:rss)).to eq user
end
it 'returns nil if feed_token is blank' do
- expect(find_user_from_feed_token).to be_nil
+ expect(find_user_from_feed_token(:rss)).to be_nil
end
it 'returns exception if invalid feed_token' do
set_param(:feed_token, 'invalid_token')
- expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ expect { find_user_from_feed_token(:rss) }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
@@ -74,34 +75,38 @@ describe Gitlab::Auth::UserAuthFinders do
it 'returns user if valid rssd_token' do
set_param(:rss_token, user.feed_token)
- expect(find_user_from_feed_token).to eq user
+ expect(find_user_from_feed_token(:rss)).to eq user
end
it 'returns nil if rss_token is blank' do
- expect(find_user_from_feed_token).to be_nil
+ expect(find_user_from_feed_token(:rss)).to be_nil
end
it 'returns exception if invalid rss_token' do
set_param(:rss_token, 'invalid_token')
- expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ expect { find_user_from_feed_token(:rss) }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
end
context 'when the request format is not atom' do
it 'returns nil' do
+ env['SCRIPT_NAME'] = 'json'
+
set_param(:feed_token, user.feed_token)
- expect(find_user_from_feed_token).to be_nil
+ expect(find_user_from_feed_token(:rss)).to be_nil
end
end
context 'when the request format is empty' do
it 'the method call does not modify the original value' do
+ env['SCRIPT_NAME'] = 'url.atom'
+
env.delete('action_dispatch.request.formats')
- find_user_from_feed_token
+ find_user_from_feed_token(:rss)
expect(env['action_dispatch.request.formats']).to be_nil
end
@@ -111,8 +116,12 @@ describe Gitlab::Auth::UserAuthFinders do
describe '#find_user_from_access_token' do
let(:personal_access_token) { create(:personal_access_token, user: user) }
+ before do
+ env['SCRIPT_NAME'] = 'url.atom'
+ end
+
it 'returns nil if no access_token present' do
- expect(find_personal_access_token).to be_nil
+ expect(find_user_from_access_token).to be_nil
end
context 'when validate_access_token! returns valid' do
@@ -131,9 +140,59 @@ describe Gitlab::Auth::UserAuthFinders do
end
end
+ describe '#find_user_from_web_access_token' do
+ let(:personal_access_token) { create(:personal_access_token, user: user) }
+
+ before do
+ env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
+ end
+
+ it 'returns exception if token has no user' do
+ allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil)
+
+ expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
+
+ context 'no feed or API requests' do
+ it 'returns nil if the request is not RSS' do
+ expect(find_user_from_web_access_token(:rss)).to be_nil
+ end
+
+ it 'returns nil if the request is not ICS' do
+ expect(find_user_from_web_access_token(:ics)).to be_nil
+ end
+
+ it 'returns nil if the request is not API' do
+ expect(find_user_from_web_access_token(:api)).to be_nil
+ end
+ end
+
+ it 'returns the user for RSS requests' do
+ env['SCRIPT_NAME'] = 'url.atom'
+
+ expect(find_user_from_web_access_token(:rss)).to eq(user)
+ end
+
+ it 'returns the user for ICS requests' do
+ env['SCRIPT_NAME'] = 'url.ics'
+
+ expect(find_user_from_web_access_token(:ics)).to eq(user)
+ end
+
+ it 'returns the user for API requests' do
+ env['SCRIPT_NAME'] = '/api/endpoint'
+
+ expect(find_user_from_web_access_token(:api)).to eq(user)
+ end
+ end
+
describe '#find_personal_access_token' do
let(:personal_access_token) { create(:personal_access_token, user: user) }
+ before do
+ env['SCRIPT_NAME'] = 'url.atom'
+ end
+
context 'passed as header' do
it 'returns token if valid personal_access_token' do
env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
@@ -220,5 +279,20 @@ describe Gitlab::Auth::UserAuthFinders do
expect { validate_access_token!(scopes: [:sudo]) }.to raise_error(Gitlab::Auth::InsufficientScopeError)
end
end
+
+ context 'with impersonation token' do
+ let(:personal_access_token) { create(:personal_access_token, :impersonation, user: user) }
+
+ context 'when impersonation is disabled' do
+ before do
+ stub_config_setting(impersonation_enabled: false)
+ allow_any_instance_of(described_class).to receive(:access_token).and_return(personal_access_token)
+ end
+
+ it 'returns Gitlab::Auth::ImpersonationDisabled' do
+ expect { validate_access_token! }.to raise_error(Gitlab::Auth::ImpersonationDisabled)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
new file mode 100644
index 00000000000..c66d7cd6148
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, :migration, schema: 20181010133639 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:group) { namespaces.create!(name: 'foo', path: 'foo') }
+ let(:subgroup) { namespaces.create!(name: 'bar', path: 'bar', parent_id: group.id) }
+
+ describe described_class::Storage::HashedProject do
+ let(:project) { double(id: 555) }
+ subject(:project_storage) { described_class.new(project) }
+
+ it 'has the correct disk_path' do
+ expect(project_storage.disk_path).to eq('@hashed/91/a7/91a73fd806ab2c005c13b4dc19130a884e909dea3f72d46e30266fe1a1f588d8')
+ end
+ end
+
+ describe described_class::Storage::LegacyProject do
+ let(:project) { double(full_path: 'this/is/the/full/path') }
+ subject(:project_storage) { described_class.new(project) }
+
+ it 'has the correct disk_path' do
+ expect(project_storage.disk_path).to eq('this/is/the/full/path')
+ end
+ end
+
+ describe described_class::Project do
+ let(:project_record) { projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz') }
+ subject(:project) { described_class.find(project_record.id) }
+
+ describe '#full_path' do
+ it 'returns path containing all parent namespaces' do
+ expect(project.full_path).to eq('foo/bar/baz')
+ end
+
+ it 'raises OrphanedNamespaceError when any parent namespace does not exist' do
+ subgroup.update_attribute(:parent_id, namespaces.maximum(:id).succ)
+
+ expect { project.full_path }.to raise_error(Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig::OrphanedNamespaceError)
+ end
+ end
+ end
+
+ describe described_class::Up do
+ describe '#perform' do
+ subject(:migrate) { described_class.new.perform(projects.minimum(:id), projects.maximum(:id)) }
+
+ it 'asks the gitaly client to set config' do
+ projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz')
+ projects.create!(namespace_id: subgroup.id, name: 'buzz', path: 'buzz', storage_version: 1)
+
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ allow(repository_service).to receive(:cleanup)
+ expect(repository_service).to receive(:set_config).with('gitlab.fullpath' => 'foo/bar/baz')
+ end
+
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ allow(repository_service).to receive(:cleanup)
+ expect(repository_service).to receive(:set_config).with('gitlab.fullpath' => 'foo/bar/buzz')
+ end
+
+ migrate
+ end
+ end
+ end
+
+ describe described_class::Down do
+ describe '#perform' do
+ subject(:migrate) { described_class.new.perform(projects.minimum(:id), projects.maximum(:id)) }
+
+ it 'asks the gitaly client to set config' do
+ projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz')
+
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ allow(repository_service).to receive(:cleanup)
+ expect(repository_service).to receive(:delete_config).with(['gitlab.fullpath'])
+ end
+
+ migrate
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
index 2a869446753..1d9bac79dcd 100644
--- a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
+++ b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
@@ -65,5 +65,30 @@ describe Gitlab::BackgroundMigration::EncryptColumns, :migration, schema: 201809
expect(hook).to have_attributes(values)
end
+
+ it 'reloads the model column information' do
+ expect(model).to receive(:reset_column_information).and_call_original
+ expect(model).to receive(:define_attribute_methods).and_call_original
+
+ subject.perform(model, [:token, :url], 1, 1)
+ end
+
+ it 'fails if a source column is not present' do
+ columns = model.columns.reject { |c| c.name == 'url' }
+ allow(model).to receive(:columns) { columns }
+
+ expect do
+ subject.perform(model, [:token, :url], 1, 1)
+ end.to raise_error(/source column: url is missing/)
+ end
+
+ it 'fails if a destination column is not present' do
+ columns = model.columns.reject { |c| c.name == 'encrypted_url' }
+ allow(model).to receive(:columns) { columns }
+
+ expect do
+ subject.perform(model, [:token, :url], 1, 1)
+ end.to raise_error(/destination column: encrypted_url is missing/)
+ end
end
end
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index e5999a1c509..be9b2588c90 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
let!(:project) { create(:project, :repository) }
let(:pipeline_status) { described_class.new(project) }
- let(:cache_key) { described_class.cache_key_for_project(project) }
+ let(:cache_key) { pipeline_status.cache_key }
describe '.load_for_project' do
it "loads the status" do
@@ -14,94 +14,24 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
end
describe 'loading in batches' do
- let(:status) { 'success' }
- let(:sha) { '424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6' }
- let(:ref) { 'master' }
- let(:pipeline_info) { { sha: sha, status: status, ref: ref } }
- let!(:project_without_status) { create(:project, :repository) }
-
describe '.load_in_batch_for_projects' do
- it 'preloads pipeline_status on projects' do
+ it 'loads pipeline_status on projects' do
described_class.load_in_batch_for_projects([project])
# Don't call the accessor that would lazy load the variable
- expect(project.instance_variable_get('@pipeline_status')).to be_a(described_class)
- end
-
- describe 'without a status in redis_cache' do
- it 'loads the status from a commit when it was not in redis_cache' do
- empty_status = { sha: nil, status: nil, ref: nil }
- fake_pipeline = described_class.new(
- project_without_status,
- pipeline_info: empty_status,
- loaded_from_cache: false
- )
-
- expect(described_class).to receive(:new)
- .with(project_without_status,
- pipeline_info: empty_status,
- loaded_from_cache: false)
- .and_return(fake_pipeline)
- expect(fake_pipeline).to receive(:load_from_project)
- expect(fake_pipeline).to receive(:store_in_cache)
-
- described_class.load_in_batch_for_projects([project_without_status])
- end
-
- it 'only connects to redis twice' do
- expect(Gitlab::Redis::Cache).to receive(:with).exactly(2).and_call_original
-
- described_class.load_in_batch_for_projects([project_without_status])
-
- expect(project_without_status.pipeline_status).not_to be_nil
- end
- end
-
- describe 'when a status was cached in redis_cache' do
- before do
- Gitlab::Redis::Cache.with do |redis|
- redis.mapped_hmset(cache_key,
- { sha: sha, status: status, ref: ref })
- end
- end
-
- it 'loads the correct status' do
- described_class.load_in_batch_for_projects([project])
-
- pipeline_status = project.instance_variable_get('@pipeline_status')
-
- expect(pipeline_status.sha).to eq(sha)
- expect(pipeline_status.status).to eq(status)
- expect(pipeline_status.ref).to eq(ref)
- end
-
- it 'only connects to redis_cache once' do
- expect(Gitlab::Redis::Cache).to receive(:with).exactly(1).and_call_original
+ project_pipeline_status = project.instance_variable_get('@pipeline_status')
- described_class.load_in_batch_for_projects([project])
-
- expect(project.pipeline_status).not_to be_nil
- end
-
- it "doesn't load the status separatly" do
- expect_any_instance_of(described_class).not_to receive(:load_from_project)
- expect_any_instance_of(described_class).not_to receive(:load_from_cache)
-
- described_class.load_in_batch_for_projects([project])
- end
+ expect(project_pipeline_status).to be_a(described_class)
+ expect(project_pipeline_status).to be_loaded
end
- end
- describe '.cached_results_for_projects' do
- it 'loads a status from caching for all projects' do
- Gitlab::Redis::Cache.with do |redis|
- redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref })
+ it 'loads 10 projects without hitting Gitaly call limit', :request_store do
+ projects = Gitlab::GitalyClient.allow_n_plus_1_calls do
+ (1..10).map { create(:project, :repository) }
end
+ Gitlab::GitalyClient.reset_counts
- result = [{ loaded_from_cache: false, pipeline_info: { sha: nil, status: nil, ref: nil } },
- { loaded_from_cache: true, pipeline_info: pipeline_info }]
-
- expect(described_class.cached_results_for_projects([project_without_status, project])).to eq(result)
+ expect { described_class.load_in_batch_for_projects(projects) }.not_to raise_error
end
end
end
@@ -198,7 +128,9 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
status_for_empty_commit.load_status
- expect(status_for_empty_commit).to be_loaded
+ expect(status_for_empty_commit.sha).to be_nil
+ expect(status_for_empty_commit.status).to be_nil
+ expect(status_for_empty_commit.ref).to be_nil
end
end
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
index 523d00c1272..5fee37bb43e 100644
--- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Build::Policy::Changes do
set(:project) { create(:project) }
describe '#satisfied_by?' do
- describe 'paths matching matching' do
+ describe 'paths matching' do
let(:pipeline) do
build(:ci_empty_pipeline, project: project,
ref: 'master',
diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index 2708d8d5b6b..541deb13b97 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -37,7 +37,7 @@ describe Gitlab::Ci::Config::External::File::Local do
end
describe '#content' do
- context 'with a a valid file' do
+ context 'with a valid file' do
let(:local_file_content) do
<<~HEREDOC
before_script:
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
index 1ccb792d1da..f54ef492e6d 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
@@ -93,7 +93,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
end
describe '#evaluate' do
- it 'returns string value it is is present' do
+ it 'returns string value if it is present' do
string = described_class.new('my string')
expect(string.evaluate).to eq 'my string'
diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
index 6259b952add..546a9e7d0cc 100644
--- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
+++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
@@ -116,6 +116,19 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
chunked_io.each_line { |line| }
end
end
+
+ context 'when buffer consist of many empty lines' do
+ let(:sample_trace_raw) { Array.new(10, " ").join("\n") }
+
+ before do
+ build.trace.set(sample_trace_raw)
+ end
+
+ it 'yields lines' do
+ expect { |b| chunked_io.each_line(&b) }
+ .to yield_successive_args(*string_io.each_line.to_a)
+ end
+ end
end
context "#read" do
@@ -143,6 +156,22 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
end
end
+ context 'when chunk is missing data' do
+ let(:length) { nil }
+
+ before do
+ stub_buffer_size(1024)
+ build.trace.set(sample_trace_raw)
+
+ # make second chunk to not have data
+ build.trace_chunks.second.append('', 0)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error described_class::FailedToGetChunkError
+ end
+ end
+
context 'when read only first 100 bytes' do
let(:length) { 100 }
@@ -266,6 +295,40 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
expect(chunked_io.readline).to eq(string_io.readline)
end
end
+
+ context 'when chunk is missing data' do
+ let(:length) { nil }
+
+ before do
+ build.trace.set(sample_trace_raw)
+
+ # make first chunk to have invalid data
+ build.trace_chunks.first.append('data', 0)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error described_class::FailedToGetChunkError
+ end
+ end
+
+ context 'when utf-8 is being used' do
+ let(:sample_trace_raw) { sample_trace_raw_utf8.force_encoding(Encoding::BINARY) }
+ let(:sample_trace_raw_utf8) { "😺\n😺\n😺\n😺" }
+
+ before do
+ stub_buffer_size(3) # the utf-8 character has 4 bytes
+
+ build.trace.set(sample_trace_raw_utf8)
+ end
+
+ it 'has known length' do
+ expect(sample_trace_raw_utf8.bytesize).to eq(4 * 4 + 3 * 1)
+ expect(sample_trace_raw.bytesize).to eq(4 * 4 + 3 * 1)
+ expect(chunked_io.size).to eq(4 * 4 + 3 * 1)
+ end
+
+ it_behaves_like 'all line matching'
+ end
end
context "#write" do
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 4f49958dd33..38626f728d7 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -257,7 +257,8 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
let!(:last_result) { stream.html_with_state }
before do
- stream.append("5678", 4)
+ data_stream.seek(4, IO::SEEK_SET)
+ data_stream.write("5678")
stream.seek(0)
end
@@ -271,25 +272,29 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
end
context 'when stream is StringIO' do
+ let(:data_stream) do
+ StringIO.new("1234")
+ end
+
let(:stream) do
- described_class.new do
- StringIO.new("1234")
- end
+ described_class.new { data_stream }
end
it_behaves_like 'html_with_states'
end
context 'when stream is ChunkedIO' do
- let(:stream) do
- described_class.new do
- Gitlab::Ci::Trace::ChunkedIO.new(build).tap do |chunked_io|
- chunked_io.write("1234")
- chunked_io.seek(0, IO::SEEK_SET)
- end
+ let(:data_stream) do
+ Gitlab::Ci::Trace::ChunkedIO.new(build).tap do |chunked_io|
+ chunked_io.write("1234")
+ chunked_io.seek(0, IO::SEEK_SET)
end
end
+ let(:stream) do
+ described_class.new { data_stream }
+ end
+
it_behaves_like 'html_with_states'
end
end
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index e1e0582cd11..8bf44acb228 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -36,7 +36,7 @@ describe Gitlab::Ci::Variables::Collection::Item do
shared_examples 'raises error for invalid type' do
it do
expect { described_class.new(key: variable_key, value: variable_value) }
- .to raise_error ArgumentError, /`#{variable_key}` must be of type String, while it was:/
+ .to raise_error ArgumentError, /`#{variable_key}` must be of type String or nil value, while it was:/
end
end
@@ -46,7 +46,7 @@ describe Gitlab::Ci::Variables::Collection::Item do
let(:variable_value) { nil }
let(:expected_value) { nil }
- it_behaves_like 'raises error for invalid type'
+ it_behaves_like 'creates variable'
end
context "when it's an empty string" do
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 6d29044ffd5..b7924302014 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -135,7 +135,7 @@ describe Gitlab::ContributionsCalendar do
expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3)
end
- context 'when the user cannot read read cross project' do
+ context 'when the user cannot read cross project' do
before do
allow(Ability).to receive(:allowed?).and_call_original
expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
diff --git a/spec/lib/gitlab/cross_project_access/check_info_spec.rb b/spec/lib/gitlab/cross_project_access/check_info_spec.rb
index 239fa364f5e..ea7393a7006 100644
--- a/spec/lib/gitlab/cross_project_access/check_info_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/check_info_spec.rb
@@ -50,7 +50,7 @@ describe Gitlab::CrossProjectAccess::CheckInfo do
expect(info.should_run?(dummy_controller)).to be_truthy
end
- it 'returns the the opposite of #should_skip? when the check is a skip' do
+ it 'returns the opposite of #should_skip? when the check is a skip' do
info = described_class.new({}, nil, nil, true)
expect(info).to receive(:should_skip?).with(dummy_controller).and_return(false)
@@ -101,7 +101,7 @@ describe Gitlab::CrossProjectAccess::CheckInfo do
expect(info.should_skip?(dummy_controller)).to be_truthy
end
- it 'returns the the opposite of #should_run? when the check is not a skip' do
+ it 'returns the opposite of #should_run? when the check is not a skip' do
info = described_class.new({}, nil, nil, false)
expect(info).to receive(:should_run?).with(dummy_controller).and_return(false)
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index 0a8c77b0ad9..b6096d4faf6 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -165,7 +165,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :
end
describe '#rename_namespace_dependencies' do
- it "moves the the repository for a project in the namespace" do
+ it "moves the repository for a project in the namespace" do
create(:project, :repository, :legacy_storage, namespace: namespace, path: "the-path-project")
expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git")
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 7d76519dddd..fc295b2deff 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -443,11 +443,17 @@ describe Gitlab::Database do
end
end
+ describe '.read_only?' do
+ it 'returns false' do
+ expect(described_class.read_only?).to be_falsey
+ end
+ end
+
describe '.db_read_only?' do
context 'when using PostgreSQL' do
before do
allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
- expect(described_class).to receive(:postgresql?).and_return(true)
+ allow(described_class).to receive(:postgresql?).and_return(true)
end
it 'detects a read only database' do
@@ -456,11 +462,25 @@ describe Gitlab::Database do
expect(described_class.db_read_only?).to be_truthy
end
+ # TODO: remove rails5-only tag after removing rails4 tests
+ it 'detects a read only database', :rails5 do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }])
+
+ expect(described_class.db_read_only?).to be_truthy
+ end
+
it 'detects a read write database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
expect(described_class.db_read_only?).to be_falsey
end
+
+ # TODO: remove rails5-only tag after removing rails4 tests
+ it 'detects a read write database', :rails5 do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }])
+
+ expect(described_class.db_read_only?).to be_falsey
+ end
end
context 'when using MySQL' do
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index 4578da70bfc..fbcf515281e 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -37,17 +37,7 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do
let(:stub_path) { '.gitignore' }
end
- shared_examples 'initializes a DiffCollection' do
- it 'returns a valid instance of a DiffCollection' do
- expect(diff_files).to be_a(Gitlab::Git::DiffCollection)
- end
- end
-
- context 'with Gitaly disabled', :disable_gitaly do
- it_behaves_like 'initializes a DiffCollection'
- end
-
- context 'with Gitaly enabled' do
- it_behaves_like 'initializes a DiffCollection'
+ it 'returns a valid instance of a DiffCollection' do
+ expect(diff_files).to be_a(Gitlab::Git::DiffCollection)
end
end
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 7296bbf5df3..97e65318059 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -16,7 +16,7 @@ describe Gitlab::Diff::InlineDiffMarker do
end
end
- context "when the text text is not html safe" do
+ context "when the text is not html safe" do
let(:rich) { "abc 'def' differs" }
it 'marks the range' do
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index 0989188f7ee..376d3accd55 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -49,7 +49,7 @@ describe Gitlab::Email::ReplyParser do
expect(test_parse_body(fixture_file("emails/paragraphs.eml")))
.to eq(
<<-BODY.strip_heredoc.chomp
- Is there any reason the *old* candy can't be be kept in silos while the new candy
+ Is there any reason the *old* candy can't be kept in silos while the new candy
is imported into *new* silos?
The thing about candy is it stays delicious for a long time -- we can just keep
diff --git a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
index 2e3656b52fb..5107e1efbbd 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
@@ -11,6 +11,14 @@ describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
let(:options) { {} }
+ context 'when unique key is not set' do
+ let(:unique_key) { }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error ArgumentError
+ end
+ end
+
context 'when the lease is not obtained yet' do
before do
stub_exclusive_lease(unique_key, 'uuid')
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index b243f0dacae..80dd3dcc58e 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -128,7 +128,7 @@ describe Gitlab::Git::Blob, :seed_helper do
end
end
- shared_examples 'finding blobs by ID' do
+ describe '.raw' do
let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) }
let(:bad_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::BigCommit::ID) }
@@ -166,16 +166,6 @@ describe Gitlab::Git::Blob, :seed_helper do
end
end
- describe '.raw' do
- context 'when the blob_raw Gitaly feature is enabled' do
- it_behaves_like 'finding blobs by ID'
- end
-
- context 'when the blob_raw Gitaly feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'finding blobs by ID'
- end
- end
-
describe '.batch' do
let(:blob_references) do
[
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 6be35eee0fd..db68062e433 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -183,110 +183,100 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
- shared_examples '.where' do
- context 'path is empty string' do
- subject do
- commits = described_class.where(
- repo: repository,
- ref: 'master',
- path: '',
- limit: 10
- )
-
- commits.map { |c| c.id }
- end
+ context 'path is empty string' do
+ subject do
+ commits = described_class.where(
+ repo: repository,
+ ref: 'master',
+ path: '',
+ limit: 10
+ )
- it 'has 10 elements' do
- expect(subject.size).to eq(10)
- end
- it { is_expected.to include(SeedRepo::EmptyCommit::ID) }
+ commits.map { |c| c.id }
end
- context 'path is nil' do
- subject do
- commits = described_class.where(
- repo: repository,
- ref: 'master',
- path: nil,
- limit: 10
- )
-
- commits.map { |c| c.id }
- end
-
- it 'has 10 elements' do
- expect(subject.size).to eq(10)
- end
- it { is_expected.to include(SeedRepo::EmptyCommit::ID) }
+ it 'has 10 elements' do
+ expect(subject.size).to eq(10)
end
+ it { is_expected.to include(SeedRepo::EmptyCommit::ID) }
+ end
- context 'ref is branch name' do
- subject do
- commits = described_class.where(
- repo: repository,
- ref: 'master',
- path: 'files',
- limit: 3,
- offset: 1
- )
+ context 'path is nil' do
+ subject do
+ commits = described_class.where(
+ repo: repository,
+ ref: 'master',
+ path: nil,
+ limit: 10
+ )
- commits.map { |c| c.id }
- end
+ commits.map { |c| c.id }
+ end
- it 'has 3 elements' do
- expect(subject.size).to eq(3)
- end
- it { is_expected.to include("d14d6c0abdd253381df51a723d58691b2ee1ab08") }
- it { is_expected.not_to include("eb49186cfa5c4338011f5f590fac11bd66c5c631") }
+ it 'has 10 elements' do
+ expect(subject.size).to eq(10)
end
+ it { is_expected.to include(SeedRepo::EmptyCommit::ID) }
+ end
- context 'ref is commit id' do
- subject do
- commits = described_class.where(
- repo: repository,
- ref: "874797c3a73b60d2187ed6e2fcabd289ff75171e",
- path: 'files',
- limit: 3,
- offset: 1
- )
+ context 'ref is branch name' do
+ subject do
+ commits = described_class.where(
+ repo: repository,
+ ref: 'master',
+ path: 'files',
+ limit: 3,
+ offset: 1
+ )
- commits.map { |c| c.id }
- end
+ commits.map { |c| c.id }
+ end
- it 'has 3 elements' do
- expect(subject.size).to eq(3)
- end
- it { is_expected.to include("2f63565e7aac07bcdadb654e253078b727143ec4") }
- it { is_expected.not_to include(SeedRepo::Commit::ID) }
+ it 'has 3 elements' do
+ expect(subject.size).to eq(3)
end
+ it { is_expected.to include("d14d6c0abdd253381df51a723d58691b2ee1ab08") }
+ it { is_expected.not_to include("eb49186cfa5c4338011f5f590fac11bd66c5c631") }
+ end
- context 'ref is tag' do
- subject do
- commits = described_class.where(
- repo: repository,
- ref: 'v1.0.0',
- path: 'files',
- limit: 3,
- offset: 1
- )
+ context 'ref is commit id' do
+ subject do
+ commits = described_class.where(
+ repo: repository,
+ ref: "874797c3a73b60d2187ed6e2fcabd289ff75171e",
+ path: 'files',
+ limit: 3,
+ offset: 1
+ )
- commits.map { |c| c.id }
- end
+ commits.map { |c| c.id }
+ end
- it 'has 3 elements' do
- expect(subject.size).to eq(3)
- end
- it { is_expected.to include("874797c3a73b60d2187ed6e2fcabd289ff75171e") }
- it { is_expected.not_to include(SeedRepo::Commit::ID) }
+ it 'has 3 elements' do
+ expect(subject.size).to eq(3)
end
+ it { is_expected.to include("2f63565e7aac07bcdadb654e253078b727143ec4") }
+ it { is_expected.not_to include(SeedRepo::Commit::ID) }
end
- describe '.where with gitaly' do
- it_should_behave_like '.where'
- end
+ context 'ref is tag' do
+ subject do
+ commits = described_class.where(
+ repo: repository,
+ ref: 'v1.0.0',
+ path: 'files',
+ limit: 3,
+ offset: 1
+ )
+
+ commits.map { |c| c.id }
+ end
- describe '.where without gitaly', :skip_gitaly_mock do
- it_should_behave_like '.where'
+ it 'has 3 elements' do
+ expect(subject.size).to eq(3)
+ end
+ it { is_expected.to include("874797c3a73b60d2187ed6e2fcabd289ff75171e") }
+ it { is_expected.not_to include(SeedRepo::Commit::ID) }
end
describe '.between' do
@@ -460,11 +450,17 @@ describe Gitlab::Git::Commit, :seed_helper do
described_class.extract_signature_lazily(repository, commit_id)
end
+ other_repository = double(:repository)
+ described_class.extract_signature_lazily(other_repository, commit_ids.first)
+
expect(described_class).to receive(:batch_signature_extraction)
.with(repository, commit_ids)
.once
.and_return({})
+ expect(described_class).not_to receive(:batch_signature_extraction)
+ .with(other_repository, commit_ids.first)
+
2.times { signatures.each(&:itself) }
end
end
@@ -508,7 +504,7 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
- shared_examples '#stats' do
+ describe '#stats' do
subject { commit.stats }
describe '#additions' do
@@ -527,14 +523,6 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
- describe '#stats with gitaly on' do
- it_should_behave_like '#stats'
- end
-
- describe '#stats with gitaly disabled', :skip_gitaly_mock do
- it_should_behave_like '#stats'
- end
-
describe '#has_zero_stats?' do
it { expect(commit.has_zero_stats?).to eq(false) }
end
@@ -577,25 +565,15 @@ describe Gitlab::Git::Commit, :seed_helper do
commit_ids.map { |id| described_class.get_message(repository, id) }
end
- shared_examples 'getting commit messages' do
- it 'gets commit messages' do
- expect(subject).to contain_exactly(
- "Added contributing guide\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
- "Add submodule\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n"
- )
- end
- end
-
- context 'when Gitaly commit_messages feature is enabled' do
- it_behaves_like 'getting commit messages'
-
- it 'gets messages in one batch', :request_store do
- expect { subject.map(&:itself) }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
- end
+ it 'gets commit messages' do
+ expect(subject).to contain_exactly(
+ "Added contributing guide\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
+ "Add submodule\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n"
+ )
end
- context 'when Gitaly commit_messages feature is disabled', :disable_gitaly do
- it_behaves_like 'getting commit messages'
+ it 'gets messages in one batch', :request_store do
+ expect { subject.map(&:itself) }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
end
end
diff --git a/spec/lib/gitlab/git/merge_base_spec.rb b/spec/lib/gitlab/git/merge_base_spec.rb
index 2f4e043a20f..8d16d451730 100644
--- a/spec/lib/gitlab/git/merge_base_spec.rb
+++ b/spec/lib/gitlab/git/merge_base_spec.rb
@@ -82,7 +82,7 @@ describe Gitlab::Git::MergeBase do
end
describe '#unknown_refs', :missing_ref do
- it 'returns the the refs passed that are not part of the repository' do
+ it 'returns the refs passed that are not part of the repository' do
expect(merge_base.unknown_refs).to contain_exactly('aaaa')
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 1fe73c12fc0..852ee9c96af 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1469,6 +1469,19 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
end
+
+ it 'writes the HEAD' do
+ repository.write_ref('HEAD', 'refs/heads/feature')
+
+ expect(repository.commit('HEAD')).to eq(repository.commit('feature'))
+ expect(repository.root_ref).to eq('feature')
+ end
+
+ it 'writes other refs' do
+ repository.write_ref('refs/heads/feature', SeedRepo::Commit::ID)
+
+ expect(repository.commit('feature').sha).to eq(SeedRepo::Commit::ID)
+ end
end
describe '#write_config' do
diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb
index c5bad062c2a..b51e3879f49 100644
--- a/spec/lib/gitlab/git/tag_spec.rb
+++ b/spec/lib/gitlab/git/tag_spec.rb
@@ -3,7 +3,7 @@ require "spec_helper"
describe Gitlab::Git::Tag, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
- shared_examples 'Gitlab::Git::Repository#tags' do
+ describe '#tags' do
describe 'first tag' do
let(:tag) { repository.tags.first }
@@ -25,14 +25,6 @@ describe Gitlab::Git::Tag, :seed_helper do
it { expect(repository.tags.size).to eq(SeedRepo::Repo::TAGS.size) }
end
- context 'when Gitaly tags feature is enabled' do
- it_behaves_like 'Gitlab::Git::Repository#tags'
- end
-
- context 'when Gitaly tags feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'Gitlab::Git::Repository#tags'
- end
-
describe '.get_message' do
let(:tag_ids) { %w[f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b] }
@@ -40,23 +32,16 @@ describe Gitlab::Git::Tag, :seed_helper do
tag_ids.map { |id| described_class.get_message(repository, id) }
end
- shared_examples 'getting tag messages' do
- it 'gets tag messages' do
- expect(subject[0]).to eq("Release\n")
- expect(subject[1]).to eq("Version 1.1.0\n")
- end
+ it 'gets tag messages' do
+ expect(subject[0]).to eq("Release\n")
+ expect(subject[1]).to eq("Version 1.1.0\n")
end
- context 'when Gitaly tag_messages feature is enabled' do
- it_behaves_like 'getting tag messages'
-
- it 'gets messages in one batch', :request_store do
- expect { subject.map(&:itself) }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
- end
- end
+ it 'gets messages in one batch', :request_store do
+ other_repository = double(:repository)
+ described_class.get_message(other_repository, tag_ids.first)
- context 'when Gitaly tag_messages feature is disabled', :disable_gitaly do
- it_behaves_like 'getting tag messages'
+ expect { subject.map(&:itself) }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
end
end
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 3792d6bf67b..bec875fb03d 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -80,18 +80,8 @@ describe Gitlab::Git::Tree, :seed_helper do
end
describe '#where' do
- shared_examples '#where' do
- it 'returns an empty array when called with an invalid ref' do
- expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([])
- end
- end
-
- context 'with gitaly' do
- it_behaves_like '#where'
- end
-
- context 'without gitaly', :skip_gitaly_mock do
- it_behaves_like '#where'
+ it 'returns an empty array when called with an invalid ref' do
+ expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([])
end
end
end
diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb
index ba7fb168a3b..3ab04a1c46d 100644
--- a/spec/lib/gitlab/git_ref_validator_spec.rb
+++ b/spec/lib/gitlab/git_ref_validator_spec.rb
@@ -27,4 +27,5 @@ describe Gitlab::GitRefValidator do
it { expect(described_class.validate('-branch')).to be_falsey }
it { expect(described_class.validate('.tag')).to be_falsey }
it { expect(described_class.validate('my branch')).to be_falsey }
+ it { expect(described_class.validate("\xA0\u0000\xB0")).to be_falsey }
end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 81bcd8c28ed..5eda4d041a8 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
# 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, skip_gitaly_mock: true do
+describe Gitlab::GitalyClient do
describe '.stub_class' do
it 'returns the gRPC health check stub' do
expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub)
@@ -191,102 +191,13 @@ describe Gitlab::GitalyClient, skip_gitaly_mock: true do
let(:feature_name) { 'my_feature' }
let(:real_feature_name) { "gitaly_#{feature_name}" }
- context 'when Gitaly is disabled' do
- before do
- allow(described_class).to receive(:enabled?).and_return(false)
- end
-
- 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 do
- Feature.get(real_feature_name).disable
- end
-
- 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 do
- Feature.get(real_feature_name).enable
- end
-
- 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 do
- Feature.get(real_feature_name).enable_percentage_of_time(70)
- end
-
- 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
-
- context "when a feature is not persisted" do
- it 'returns false when opt_into_all_features is off' do
- allow(Feature).to receive(:persisted?).and_return(false)
- allow(described_class).to receive(:opt_into_all_features?).and_return(false)
-
- expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
- end
-
- it 'returns true when the override is on' do
- allow(Feature).to receive(:persisted?).and_return(false)
- allow(described_class).to receive(:opt_into_all_features?).and_return(true)
-
- expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
- end
- end
+ before do
+ allow(Feature).to receive(:enabled?).and_return(false)
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 do
- Feature.get(real_feature_name).disable
- end
-
- it 'returns false' do
- expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
- end
- end
+ it 'returns false' do
+ expect(Feature).to receive(:enabled?).with(real_feature_name)
+ expect(described_class.feature_enabled?(feature_name)).to be(false)
end
end
@@ -305,4 +216,29 @@ describe Gitlab::GitalyClient, skip_gitaly_mock: true do
end
end
end
+
+ describe 'Peek Performance bar details' do
+ let(:gitaly_server) { Gitaly::Server.all.first }
+
+ before do
+ Gitlab::SafeRequestStore[:peek_enabled] = true
+ end
+
+ context 'when the request store is active', :request_store do
+ it 'records call details if a RPC is called' do
+ gitaly_server.server_version
+
+ expect(described_class.list_call_details).not_to be_empty
+ expect(described_class.list_call_details.size).to be(1)
+ end
+ end
+
+ context 'when no request store is active' do
+ it 'records nothing' do
+ gitaly_server.server_version
+
+ expect(described_class.list_call_details).to be_empty
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index d8f01dcb76b..77f5b2ffa37 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -218,7 +218,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
describe '#fail_import' do
it 'marks the import as failed' do
- expect(project).to receive(:mark_import_as_failed).with('foo')
+ expect(project.import_state).to receive(:mark_as_failed).with('foo')
expect(importer.fail_import('foo')).to eq(false)
end
diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
index 20b48c1de68..f5df38c9aaf 100644
--- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
@@ -36,7 +36,7 @@ describe Gitlab::GithubImport::ParallelImporter do
it 'updates the import JID of the project' do
importer.execute
- expect(project.reload.import_jid).to eq("github-importer/#{project.id}")
+ expect(project.import_state.reload.jid).to eq("github-importer/#{project.id}")
end
end
end
diff --git a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
new file mode 100644
index 00000000000..4609593ef6a
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Gitlab::Graphql::Loaders::BatchModelLoader do
+ describe '#find' do
+ let(:issue) { create(:issue) }
+ let(:user) { create(:user) }
+
+ it 'finds a model by id' do
+ issue_result = described_class.new(Issue, issue.id).find
+ user_result = described_class.new(User, user.id).find
+
+ expect(issue_result.__sync).to eq(issue)
+ expect(user_result.__sync).to eq(user)
+ end
+
+ it 'only queries once per model' do
+ other_user = create(:user)
+ user
+ issue
+
+ expect do
+ [described_class.new(User, other_user.id).find,
+ described_class.new(User, user.id).find,
+ described_class.new(Issue, issue.id).find].map(&:__sync)
+ end.not_to exceed_query_limit(2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 1d184375a52..8d2f60d7a8b 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -245,6 +245,7 @@ project:
- protected_branches
- protected_tags
- project_members
+- project_repository
- users
- requesters
- deploy_keys_projects
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 8bce7a4cdf5..c7f92cbb143 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -40,6 +40,7 @@ describe Gitlab::Kubernetes::Helm::Api do
allow(client).to receive(:create_config_map).and_return(nil)
allow(client).to receive(:create_service_account).and_return(nil)
allow(client).to receive(:create_cluster_role_binding).and_return(nil)
+ allow(client).to receive(:delete_pod).and_return(nil)
allow(namespace).to receive(:ensure_exists!).once
end
@@ -50,6 +51,13 @@ describe Gitlab::Kubernetes::Helm::Api do
subject.install(command)
end
+ it 'removes an existing pod before installing' do
+ expect(client).to receive(:delete_pod).with('install-app-name', 'gitlab-managed-apps').once.ordered
+ expect(client).to receive(:create_pod).once.ordered
+
+ subject.install(command)
+ end
+
context 'with a ConfigMap' do
let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application_name, files).generate }
@@ -180,6 +188,7 @@ describe Gitlab::Kubernetes::Helm::Api do
allow(client).to receive(:update_config_map).and_return(nil)
allow(client).to receive(:create_pod).and_return(nil)
+ allow(client).to receive(:delete_pod).and_return(nil)
end
it 'ensures the namespace exists before creating the pod' do
@@ -189,6 +198,13 @@ describe Gitlab::Kubernetes::Helm::Api do
subject.update(command)
end
+ it 'removes an existing pod before updating' do
+ expect(client).to receive(:delete_pod).with('upgrade-app-name', 'gitlab-managed-apps').once.ordered
+ expect(client).to receive(:create_pod).once.ordered
+
+ subject.update(command)
+ end
+
it 'updates the config map on kubeclient when one exists' do
resource = Gitlab::Kubernetes::ConfigMap.new(
application_name, files
@@ -224,9 +240,18 @@ describe Gitlab::Kubernetes::Helm::Api do
describe '#delete_pod!' do
it 'deletes the POD from kubernetes cluster' do
- expect(client).to receive(:delete_pod).with(command.pod_name, gitlab_namespace).once
+ expect(client).to receive(:delete_pod).with('install-app-name', 'gitlab-managed-apps').once
- subject.delete_pod!(command.pod_name)
+ subject.delete_pod!('install-app-name')
+ end
+
+ context 'when the resource being deleted does not exist' do
+ it 'catches the error' do
+ expect(client).to receive(:delete_pod).with('install-app-name', 'gitlab-managed-apps')
+ .and_raise(Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil))
+
+ subject.delete_pod!('install-app-name')
+ end
end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 39852b7fe29..82ed4d47857 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -43,6 +43,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
--tls-cert /data/helm/app-name/config/cert.pem
--tls-key /data/helm/app-name/config/key.pem
--version 1.2.3
+ --set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
EOS
@@ -101,6 +102,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
--tls-cert /data/helm/app-name/config/cert.pem
--tls-key /data/helm/app-name/config/key.pem
--version 1.2.3
+ --set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
EOS
@@ -126,7 +128,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
<<~EOS.strip
/bin/date
/bin/true
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml
+ helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml
EOS
end
end
@@ -148,7 +150,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:helm_install_command) do
<<~EOS.strip
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml
+ helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml
/bin/date
/bin/false
EOS
@@ -175,6 +177,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
helm install chart-name
--name app-name
--version 1.2.3
+ --set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
EOS
@@ -204,6 +207,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
--tls-ca-cert /data/helm/app-name/config/ca.pem
--tls-cert /data/helm/app-name/config/cert.pem
--tls-key /data/helm/app-name/config/key.pem
+ --set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
EOS
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index 20514486727..d2df21d7bb5 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -174,7 +174,7 @@ describe Gitlab::LegacyGithubImport::Importer do
described_class.new(project).execute
- expect(project.import_error).to eq error.to_json
+ expect(project.import_state.last_error).to eq error.to_json
end
end
diff --git a/spec/lib/gitlab/multi_collection_paginator_spec.rb b/spec/lib/gitlab/multi_collection_paginator_spec.rb
index 68bd4f93159..28cd704b05a 100644
--- a/spec/lib/gitlab/multi_collection_paginator_spec.rb
+++ b/spec/lib/gitlab/multi_collection_paginator_spec.rb
@@ -28,7 +28,7 @@ describe Gitlab::MultiCollectionPaginator do
expect(paginator.paginate(1)).to eq(all_projects.take(3))
end
- it 'fils the second page with a mixture of of the first & second collection' do
+ it 'fils the second page with a mixture of the first & second collection' do
first_collection_element = all_projects.last
second_collection_elements = all_groups.take(2)
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 4059188fba1..8bb0c1a0b8a 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -43,31 +43,16 @@ describe Gitlab::Profiler do
it 'uses the user for auth if given' do
user = double(:user)
- user_token = 'user'
- allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck, :first).and_return(user_token)
-
- expect(app).to receive(:get).with('/', nil, 'Private-Token' => user_token)
- expect(app).to receive(:get).with('/api/v4/users')
+ expect(described_class).to receive(:with_user).with(user)
described_class.profile('/', user: user)
end
- context 'when providing a user without a personal access token' do
- it 'raises an error' do
- user = double(:user)
- allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck).and_return([])
-
- expect { described_class.profile('/', user: user) }.to raise_error('Your user must have a personal_access_token')
- end
- end
-
it 'uses the private_token for auth if both it and user are set' do
user = double(:user)
- user_token = 'user'
-
- allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck, :first).and_return(user_token)
+ expect(described_class).to receive(:with_user).with(nil).and_call_original
expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token)
expect(app).to receive(:get).with('/api/v4/users')
@@ -210,6 +195,29 @@ describe Gitlab::Profiler do
end
end
+ describe '.with_user' do
+ context 'when the user is set' do
+ let(:user) { double(:user) }
+
+ it 'overrides auth in ApplicationController to use the given user' do
+ expect(described_class.with_user(user) { ApplicationController.new.current_user }).to eq(user)
+ end
+
+ it 'cleans up ApplicationController afterwards' do
+ expect { described_class.with_user(user) { } }
+ .to not_change { ActionController.instance_methods(false) }
+ end
+ end
+
+ context 'when the user is nil' do
+ it 'does not define methods on ApplicationController' do
+ expect(ApplicationController).not_to receive(:define_method)
+
+ described_class.with_user(nil) { }
+ end
+ end
+ end
+
describe '.log_load_times_by_model' do
it 'logs the model, query count, and time by slowest first' do
expect(null_logger).to receive(:load_times_by_model).and_return(
diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb
new file mode 100644
index 00000000000..78974cadb69
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Prometheus::QueryVariables do
+ describe '.call' do
+ set(:environment) { create(:environment) }
+ let(:slug) { environment.slug }
+
+ subject { described_class.call(environment) }
+
+ it { is_expected.to include(ci_environment_slug: slug) }
+
+ it do
+ is_expected.to include(environment_filter:
+ %{container_name!="POD",environment="#{slug}"})
+ end
+
+ context 'without deployment platform' do
+ it { is_expected.to include(kube_namespace: '') }
+ end
+
+ context 'with deplyoment platform' do
+ let(:kube_namespace) { environment.deployment_platform.actual_namespace }
+
+ before do
+ create(:cluster, :provided_by_user, projects: [environment.project])
+ end
+
+ it { is_expected.to include(kube_namespace: kube_namespace) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 8df0facdab3..39e0a17a307 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -10,8 +10,8 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?(import_url)).to be false
end
- it 'allows imports from configured SSH host and port' do
- import_url = "http://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git"
+ it 'allows mirroring from configured SSH host and port' do
+ import_url = "ssh://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git"
expect(described_class.blocked_url?(import_url)).to be false
end
@@ -29,24 +29,46 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true
end
+ it 'returns true for bad protocol on configured web/SSH host and ports' do
+ web_url = "javascript://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git%0aalert(1)"
+ expect(described_class.blocked_url?(web_url)).to be true
+
+ ssh_url = "javascript://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git%0aalert(1)"
+ expect(described_class.blocked_url?(ssh_url)).to be true
+ end
+
it 'returns true for localhost IPs' do
+ expect(described_class.blocked_url?('https://[0:0:0:0:0:0:0:0]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://0.0.0.0/foo/foo.git')).to be true
- expect(described_class.blocked_url?('https://[::1]/foo/foo.git')).to be true
- expect(described_class.blocked_url?('https://127.0.0.1/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::]/foo/foo.git')).to be true
end
it 'returns true for loopback IP' do
expect(described_class.blocked_url?('https://127.0.0.2/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://127.0.0.1/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::1]/foo/foo.git')).to be true
end
it 'returns true for alternative version of 127.0.0.1 (0177.1)' do
expect(described_class.blocked_url?('https://0177.1:65535/foo/foo.git')).to be true
end
+ it 'returns true for alternative version of 127.0.0.1 (017700000001)' do
+ expect(described_class.blocked_url?('https://017700000001:65535/foo/foo.git')).to be true
+ end
+
it 'returns true for alternative version of 127.0.0.1 (0x7f.1)' do
expect(described_class.blocked_url?('https://0x7f.1:65535/foo/foo.git')).to be true
end
+ it 'returns true for alternative version of 127.0.0.1 (0x7f.0.0.1)' do
+ expect(described_class.blocked_url?('https://0x7f.0.0.1:65535/foo/foo.git')).to be true
+ end
+
+ it 'returns true for alternative version of 127.0.0.1 (0x7f000001)' do
+ expect(described_class.blocked_url?('https://0x7f000001:65535/foo/foo.git')).to be true
+ end
+
it 'returns true for alternative version of 127.0.0.1 (2130706433)' do
expect(described_class.blocked_url?('https://2130706433:65535/foo/foo.git')).to be true
end
@@ -55,6 +77,27 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://127.000.000.001:65535/foo/foo.git')).to be true
end
+ it 'returns true for alternative version of 127.0.0.1 (127.0.1)' do
+ expect(described_class.blocked_url?('https://127.0.1:65535/foo/foo.git')).to be true
+ end
+
+ context 'with ipv6 mapped address' do
+ it 'returns true for localhost IPs' do
+ expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:0.0.0.0]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:0.0.0.0]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:0:0]/foo/foo.git')).to be true
+ end
+
+ it 'returns true for loopback IPs' do
+ expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:127.0.0.1]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:127.0.0.1]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:7f00:1]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:127.0.0.2]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:127.0.0.2]/foo/foo.git')).to be true
+ expect(described_class.blocked_url?('https://[::ffff:7f00:2]/foo/foo.git')).to be true
+ end
+ end
+
it 'returns true for a non-alphanumeric hostname' do
stub_resolv
@@ -78,7 +121,22 @@ describe Gitlab::UrlBlocker do
end
context 'when allow_local_network is' do
- let(:local_ips) { ['192.168.1.2', '10.0.0.2', '172.16.0.2'] }
+ let(:local_ips) do
+ [
+ '192.168.1.2',
+ '[0:0:0:0:0:ffff:192.168.1.2]',
+ '[::ffff:c0a8:102]',
+ '10.0.0.2',
+ '[0:0:0:0:0:ffff:10.0.0.2]',
+ '[::ffff:a00:2]',
+ '172.16.0.2',
+ '[0:0:0:0:0:ffff:172.16.0.2]',
+ '[::ffff:ac10:20]',
+ '[feef::1]',
+ '[fee2::]',
+ '[fc00:bf8b:e62c:abcd:abcd:aaaa:aaaa:aaaa]'
+ ]
+ end
let(:fake_domain) { 'www.fakedomain.fake' }
context 'true (default)' do
@@ -109,10 +167,14 @@ describe Gitlab::UrlBlocker do
expect(described_class).not_to be_blocked_url('http://169.254.168.100')
end
- # This is blocked due to the hostname check: https://gitlab.com/gitlab-org/gitlab-ce/issues/50227
- it 'blocks IPv6 link-local endpoints' do
- expect(described_class).to be_blocked_url('http://[::ffff:169.254.169.254]')
- expect(described_class).to be_blocked_url('http://[::ffff:169.254.168.100]')
+ it 'allows IPv6 link-local endpoints' do
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]')
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.169.254]')
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a9fe]')
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]')
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.168.100]')
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a864]')
+ expect(described_class).not_to be_blocked_url('http://[fe80::c800:eff:fe74:8]')
end
end
@@ -135,14 +197,20 @@ describe Gitlab::UrlBlocker do
end
it 'blocks IPv6 link-local endpoints' do
+ expect(described_class).to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[::ffff:169.254.169.254]', allow_local_network: false)
+ expect(described_class).to be_blocked_url('http://[::ffff:a9fe:a9fe]', allow_local_network: false)
+ expect(described_class).to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[::ffff:169.254.168.100]', allow_local_network: false)
- expect(described_class).to be_blocked_url('http://[FE80::C800:EFF:FE74:8]', allow_local_network: false)
+ expect(described_class).to be_blocked_url('http://[::ffff:a9fe:a864]', allow_local_network: false)
+ expect(described_class).to be_blocked_url('http://[fe80::c800:eff:fe74:8]', allow_local_network: false)
end
end
def stub_domain_resolv(domain, ip)
- allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)])
+ address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)
+ allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
+ allow(address).to receive(:ipv6_v4mapped?).and_return(false)
end
def unstub_domain_resolv
@@ -183,6 +251,36 @@ describe Gitlab::UrlBlocker do
end
end
+ describe '#validate_hostname!' do
+ let(:ip_addresses) do
+ [
+ '2001:db8:1f70::999:de8:7648:6e8',
+ 'FE80::C800:EFF:FE74:8',
+ '::ffff:127.0.0.1',
+ '::ffff:169.254.168.100',
+ '::ffff:7f00:1',
+ '0:0:0:0:0:ffff:0.0.0.0',
+ 'localhost',
+ '127.0.0.1',
+ '127.000.000.001',
+ '0x7f000001',
+ '0x7f.0.0.1',
+ '0x7f.0.0.1',
+ '017700000001',
+ '0177.1',
+ '2130706433',
+ '::',
+ '::1'
+ ]
+ end
+
+ it 'does not raise error for valid Ip addresses' do
+ ip_addresses.each do |ip|
+ expect { described_class.send(:validate_hostname!, ip) }.not_to raise_error
+ end
+ end
+ end
+
# Resolv does not support resolving UTF-8 domain names
# See https://bugs.ruby-lang.org/issues/4270
def stub_resolv
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index b212d2b05f2..5390f237073 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -19,6 +19,7 @@ describe Gitlab::UsageData do
create(:cluster, :provided_by_user, :disabled)
create(:clusters_applications_helm, :installed, cluster: gcp_cluster)
create(:clusters_applications_ingress, :installed, cluster: gcp_cluster)
+ create(:clusters_applications_cert_managers, :installed, cluster: gcp_cluster)
create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster)
create(:clusters_applications_runner, :installed, cluster: gcp_cluster)
create(:clusters_applications_knative, :installed, cluster: gcp_cluster)
@@ -81,6 +82,7 @@ describe Gitlab::UsageData do
clusters_platforms_user
clusters_applications_helm
clusters_applications_ingress
+ clusters_applications_cert_managers
clusters_applications_prometheus
clusters_applications_runner
clusters_applications_knative
@@ -131,6 +133,7 @@ describe Gitlab::UsageData do
expect(count_data[:clusters_platforms_user]).to eq(1)
expect(count_data[:clusters_applications_helm]).to eq(1)
expect(count_data[:clusters_applications_ingress]).to eq(1)
+ expect(count_data[:clusters_applications_cert_managers]).to eq(1)
expect(count_data[:clusters_applications_prometheus]).to eq(1)
expect(count_data[:clusters_applications_runner]).to eq(1)
expect(count_data[:clusters_applications_knative]).to eq(1)
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index ff1a5aa2536..150c00e4bfe 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -522,7 +522,7 @@ describe Notify do
let(:project_snippet) { create(:project_snippet, project: project) }
let(:project_snippet_note) { create(:note_on_project_snippet, project: project, noteable: project_snippet) }
- subject { described_class.note_snippet_email(project_snippet_note.author_id, project_snippet_note.id) }
+ subject { described_class.note_project_snippet_email(project_snippet_note.author_id, project_snippet_note.id) }
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
let(:model) { project_snippet }
diff --git a/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb b/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb
new file mode 100644
index 00000000000..34f4a36d63d
--- /dev/null
+++ b/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require Rails.root.join('db', 'post_migrate', '20181010133639_backfill_store_project_full_path_in_repo.rb')
+
+describe BackfillStoreProjectFullPathInRepo, :migration do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:group) { namespaces.create!(name: 'foo', path: 'foo') }
+ let(:subgroup) { namespaces.create!(name: 'bar', path: 'bar', parent_id: group.id) }
+
+ subject(:migration) { described_class.new }
+
+ around do |example|
+ Sidekiq::Testing.inline! do
+ example.run
+ end
+ end
+
+ describe '#up' do
+ shared_examples_for 'writes the full path to git config' do
+ it 'writes the git config' do
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ allow(repository_service).to receive(:cleanup)
+ expect(repository_service).to receive(:set_config).with('gitlab.fullpath' => expected_path)
+ end
+
+ migration.up
+ end
+
+ it 'retries in case of failure' do
+ repository_service = spy(:repository_service)
+
+ allow(Gitlab::GitalyClient::RepositoryService).to receive(:new).and_return(repository_service)
+
+ allow(repository_service).to receive(:set_config).and_raise(GRPC::BadStatus, 'Retry me')
+ expect(repository_service).to receive(:set_config).exactly(3).times
+
+ migration.up
+ end
+
+ it 'cleans up repository before writing the config' do
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
+ expect(repository_service).to receive(:cleanup).ordered
+ expect(repository_service).to receive(:set_config).ordered
+ end
+
+ migration.up
+ end
+
+ context 'legacy storage' do
+ it 'finds the repository at the correct location' do
+ Project.find(project.id).create_repository
+
+ expect { migration.up }.not_to raise_error
+ end
+ end
+
+ context 'hashed storage' do
+ it 'finds the repository at the correct location' do
+ project.update_attribute(:storage_version, 1)
+
+ Project.find(project.id).create_repository
+
+ expect { migration.up }.not_to raise_error
+ end
+ end
+ end
+
+ context 'project in group' do
+ let!(:project) { projects.create!(namespace_id: group.id, name: 'baz', path: 'baz') }
+ let(:expected_path) { 'foo/baz' }
+
+ it_behaves_like 'writes the full path to git config'
+ end
+
+ context 'project in subgroup' do
+ let!(:project) { projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz') }
+ let(:expected_path) { 'foo/bar/baz' }
+
+ it_behaves_like 'writes the full path to git config'
+ end
+ end
+
+ describe '#down' do
+ context 'project in group' do
+ let!(:project) { projects.create!(namespace_id: group.id, name: 'baz', path: 'baz') }
+
+ it 'deletes the gitlab full config value' do
+ expect_any_instance_of(Gitlab::GitalyClient::RepositoryService)
+ .to receive(:delete_config).with(['gitlab.fullpath'])
+
+ migration.down
+ end
+ end
+ end
+end
diff --git a/spec/migrations/cleanup_environments_external_url_spec.rb b/spec/migrations/cleanup_environments_external_url_spec.rb
new file mode 100644
index 00000000000..07ddaf3d38f
--- /dev/null
+++ b/spec/migrations/cleanup_environments_external_url_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20181108091549_cleanup_environments_external_url.rb')
+
+describe CleanupEnvironmentsExternalUrl, :migration do
+ let(:environments) { table(:environments) }
+ let(:invalid_entries) { environments.where(environments.arel_table[:external_url].matches('javascript://%')) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+
+ before do
+ namespace = namespaces.create(name: 'foo', path: 'foo')
+ project = projects.create!(namespace_id: namespace.id)
+
+ environments.create!(id: 1, project_id: project.id, name: 'poisoned', slug: 'poisoned', external_url: 'javascript://alert("1")')
+ end
+
+ it 'clears every environment with a javascript external_url' do
+ expect do
+ subject.up
+ end.to change { invalid_entries.count }.from(1).to(0)
+ end
+
+ it 'do not removes environments' do
+ expect do
+ subject.up
+ end.not_to change { environments.count }
+ end
+end
diff --git a/spec/migrations/migrate_forbidden_redirect_uris_spec.rb b/spec/migrations/migrate_forbidden_redirect_uris_spec.rb
new file mode 100644
index 00000000000..0bc13a3974a
--- /dev/null
+++ b/spec/migrations/migrate_forbidden_redirect_uris_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20181026091631_migrate_forbidden_redirect_uris.rb')
+
+describe MigrateForbiddenRedirectUris, :migration do
+ let(:oauth_application) { table(:oauth_applications) }
+ let(:oauth_access_grant) { table(:oauth_access_grants) }
+
+ let!(:control_app) { oauth_application.create(random_params) }
+ let!(:control_access_grant) { oauth_application.create(random_params) }
+ let!(:forbidden_js_app) { oauth_application.create(random_params.merge(redirect_uri: 'javascript://alert()')) }
+ let!(:forbidden_vb_app) { oauth_application.create(random_params.merge(redirect_uri: 'VBSCRIPT://alert()')) }
+ let!(:forbidden_access_grant) { oauth_application.create(random_params.merge(redirect_uri: 'vbscript://alert()')) }
+
+ context 'oauth application' do
+ it 'migrates forbidden javascript URI' do
+ expect { migrate! }.to change { forbidden_js_app.reload.redirect_uri }.to('http://forbidden-scheme-has-been-overwritten')
+ end
+
+ it 'migrates forbidden VBScript URI' do
+ expect { migrate! }.to change { forbidden_vb_app.reload.redirect_uri }.to('http://forbidden-scheme-has-been-overwritten')
+ end
+
+ it 'does not migrate a valid URI' do
+ expect { migrate! }.not_to change { control_app.reload.redirect_uri }
+ end
+ end
+
+ context 'access grant' do
+ it 'migrates forbidden VBScript URI' do
+ expect { migrate! }.to change { forbidden_access_grant.reload.redirect_uri }.to('http://forbidden-scheme-has-been-overwritten')
+ end
+
+ it 'does not migrate a valid URI' do
+ expect { migrate! }.not_to change { control_access_grant.reload.redirect_uri }
+ end
+ end
+
+ def random_params
+ {
+ name: 'test',
+ secret: 'test',
+ uid: Doorkeeper::OAuth::Helpers::UniqueToken.generate,
+ redirect_uri: 'http://valid.com'
+ }
+ end
+end
diff --git a/spec/migrations/migrate_issues_to_ghost_user_spec.rb b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
index 9220b49a736..0016f058a17 100644
--- a/spec/migrations/migrate_issues_to_ghost_user_spec.rb
+++ b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
@@ -18,33 +18,33 @@ describe MigrateIssuesToGhostUser, :migration do
let!(:ghost) { users.create(ghost: true, email: 'ghost@example.com') }
it 'does not create a new user' do
- expect { schema_migrate_up! }.not_to change { User.count }
+ expect { migrate! }.not_to change { User.count }
end
it 'migrates issues where author = nil to the ghost user' do
- schema_migrate_up!
+ migrate!
expect(issues.first.reload.author_id).to eq(ghost.id)
end
it 'does not change issues authored by an existing user' do
- expect { schema_migrate_up! }.not_to change { issues.second.reload.author_id}
+ expect { migrate! }.not_to change { issues.second.reload.author_id}
end
end
context 'when ghost user does not exist' do
it 'creates a new user' do
- expect { schema_migrate_up! }.to change { User.count }.by(1)
+ expect { migrate! }.to change { User.count }.by(1)
end
it 'migrates issues where author = nil to the ghost user' do
- schema_migrate_up!
+ migrate!
expect(issues.first.reload.author_id).to eq(User.ghost.id)
end
it 'does not change issues authored by an existing user' do
- expect { schema_migrate_up! }.not_to change { issues.second.reload.author_id}
+ expect { migrate! }.not_to change { issues.second.reload.author_id}
end
end
end
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 81e35e6c931..ed93f94d893 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -18,14 +18,23 @@ describe Blob do
describe '.lazy' do
let(:project) { create(:project, :repository) }
- let(:commit) { project.commit_by(oid: 'e63f41fe459e62e1228fcef60d7189127aeba95a') }
+ let(:other_project) { create(:project, :repository) }
+ let(:commit_id) { 'e63f41fe459e62e1228fcef60d7189127aeba95a' }
- it 'fetches all blobs when the first is accessed' do
- changelog = described_class.lazy(project, commit.id, 'CHANGELOG')
- contributing = described_class.lazy(project, commit.id, 'CONTRIBUTING.md')
+ it 'does not fetch blobs when none are accessed' do
+ expect(project.repository).not_to receive(:blobs_at)
- expect(Gitlab::Git::Blob).to receive(:batch).once.and_call_original
- expect(Gitlab::Git::Blob).not_to receive(:find)
+ described_class.lazy(project, commit_id, 'CHANGELOG')
+ end
+
+ it 'fetches all blobs for the same repository when one is accessed' do
+ expect(project.repository).to receive(:blobs_at).with([[commit_id, 'CHANGELOG'], [commit_id, 'CONTRIBUTING.md']]).once.and_call_original
+ expect(other_project.repository).not_to receive(:blobs_at)
+
+ changelog = described_class.lazy(project, commit_id, 'CHANGELOG')
+ contributing = described_class.lazy(project, commit_id, 'CONTRIBUTING.md')
+
+ described_class.lazy(other_project, commit_id, 'CHANGELOG')
# Access property so the values are loaded
changelog.id
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index 915bf134d57..859287bb0c8 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -45,11 +45,11 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
is_expected.to eq(%w[redis database fog])
end
- it 'returns redis store as the the lowest precedence' do
+ it 'returns redis store as the lowest precedence' do
expect(subject.first).to eq('redis')
end
- it 'returns fog store as the the highest precedence' do
+ it 'returns fog store as the highest precedence' do
expect(subject.last).to eq('fog')
end
end
@@ -436,32 +436,47 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :redis }
context 'when data exists' do
- let(:data) { 'Sample data in redis' }
-
before do
build_trace_chunk.send(:unsafe_set_data!, data)
end
- it 'persists the data' do
- expect(build_trace_chunk.redis?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to eq(data)
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+ context 'when data size reached CHUNK_SIZE' do
+ let(:data) { 'a' * described_class::CHUNK_SIZE }
- subject
+ it 'persists the data' do
+ expect(build_trace_chunk.redis?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to eq(data)
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+
+ subject
- expect(build_trace_chunk.fog?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ expect(build_trace_chunk.fog?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ end
+
+ it_behaves_like 'Atomic operation'
end
- it_behaves_like 'Atomic operation'
+ context 'when data size has not reached CHUNK_SIZE' do
+ let(:data) { 'Sample data in redis' }
+
+ it 'does not persist the data and the orignal data is intact' do
+ expect { subject }.to raise_error(described_class::FailedToPersistDataError)
+
+ expect(build_trace_chunk.redis?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to eq(data)
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+ end
+ end
end
context 'when data does not exist' do
it 'does not persist' do
- expect { subject }.to raise_error('Can not persist empty data')
+ expect { subject }.to raise_error(described_class::FailedToPersistDataError)
end
end
end
@@ -470,32 +485,47 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :database }
context 'when data exists' do
- let(:data) { 'Sample data in database' }
-
before do
build_trace_chunk.send(:unsafe_set_data!, data)
end
- it 'persists the data' do
- expect(build_trace_chunk.database?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to eq(data)
- expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+ context 'when data size reached CHUNK_SIZE' do
+ let(:data) { 'a' * described_class::CHUNK_SIZE }
- subject
+ it 'persists the data' do
+ expect(build_trace_chunk.database?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to eq(data)
+ expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
- expect(build_trace_chunk.fog?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ subject
+
+ expect(build_trace_chunk.fog?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ end
+
+ it_behaves_like 'Atomic operation'
end
- it_behaves_like 'Atomic operation'
+ context 'when data size has not reached CHUNK_SIZE' do
+ let(:data) { 'Sample data in database' }
+
+ it 'does not persist the data and the orignal data is intact' do
+ expect { subject }.to raise_error(described_class::FailedToPersistDataError)
+
+ expect(build_trace_chunk.database?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to eq(data)
+ expect { Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk) }.to raise_error(Excon::Error::NotFound)
+ end
+ end
end
context 'when data does not exist' do
it 'does not persist' do
- expect { subject }.to raise_error('Can not persist empty data')
+ expect { subject }.to raise_error(described_class::FailedToPersistDataError)
end
end
end
@@ -504,27 +534,37 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :fog }
context 'when data exists' do
- let(:data) { 'Sample data in fog' }
-
before do
build_trace_chunk.send(:unsafe_set_data!, data)
end
- it 'does not change data store' do
- expect(build_trace_chunk.fog?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ context 'when data size reached CHUNK_SIZE' do
+ let(:data) { 'a' * described_class::CHUNK_SIZE }
- subject
+ it 'does not change data store' do
+ expect(build_trace_chunk.fog?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
- expect(build_trace_chunk.fog?).to be_truthy
- expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
- expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ subject
+
+ expect(build_trace_chunk.fog?).to be_truthy
+ expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
+ expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
+ end
+
+ it_behaves_like 'Atomic operation'
end
- it_behaves_like 'Atomic operation'
+ context 'when data size has not reached CHUNK_SIZE' do
+ let(:data) { 'Sample data in fog' }
+
+ it 'does not raise error' do
+ expect { subject }.not_to raise_error
+ end
+ end
end
end
end
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
new file mode 100644
index 00000000000..170c6001eaf
--- /dev/null
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -0,0 +1,79 @@
+require 'rails_helper'
+
+describe Clusters::Applications::CertManager do
+ let(:cert_manager) { create(:clusters_applications_cert_managers) }
+
+ include_examples 'cluster application core specs', :clusters_applications_cert_managers
+
+ describe '#make_installing!' do
+ before do
+ application.make_installing!
+ end
+
+ context 'application install previously errored with older version' do
+ let(:application) { create(:clusters_applications_cert_managers, :scheduled, version: 'v0.4.0') }
+
+ it 'updates the application version' do
+ expect(application.reload.version).to eq('v0.5.0')
+ end
+ end
+ end
+
+ describe '#install_command' do
+ let(:cluster_issuer_file) { { "cluster_issuer.yaml": "---\napiVersion: certmanager.k8s.io/v1alpha1\nkind: ClusterIssuer\nmetadata:\n name: letsencrypt-prod\nspec:\n acme:\n server: https://acme-v02.api.letsencrypt.org/directory\n email: admin@example.com\n privateKeySecretRef:\n name: letsencrypt-prod\n http01: {}\n" } }
+ subject { cert_manager.install_command }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+
+ it 'should be initialized with cert_manager arguments' do
+ expect(subject.name).to eq('certmanager')
+ expect(subject.chart).to eq('stable/cert-manager')
+ expect(subject.version).to eq('v0.5.0')
+ expect(subject).not_to be_rbac
+ expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file))
+ expect(subject.postinstall).to eq(['/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml'])
+ end
+
+ context 'for a specific user' do
+ before do
+ cert_manager.email = 'abc@xyz.com'
+ cluster_issuer_file[:'cluster_issuer.yaml'].gsub! 'admin@example.com', 'abc@xyz.com'
+ end
+
+ it 'should use his/her email to register issuer with certificate provider' do
+ expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file))
+ end
+ end
+
+ context 'on a rbac enabled cluster' do
+ before do
+ cert_manager.cluster.platform_kubernetes.rbac!
+ end
+
+ it { is_expected.to be_rbac }
+ end
+
+ context 'application failed to install previously' do
+ let(:cert_manager) { create(:clusters_applications_cert_managers, :errored, version: '0.0.1') }
+
+ it 'should be initialized with the locked version' do
+ expect(subject.version).to eq('v0.5.0')
+ end
+ end
+ end
+
+ describe '#files' do
+ let(:application) { cert_manager }
+ let(:values) { subject[:'values.yaml'] }
+
+ subject { application.files }
+
+ it 'should include cert_manager specific keys in the values.yaml file' do
+ expect(values).to include('ingressShim')
+ end
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:email) }
+ end
+end
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 6b0b23eeab3..cfe0e216c78 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -5,7 +5,7 @@ describe Clusters::Applications::Ingress do
include_examples 'cluster application core specs', :clusters_applications_ingress
include_examples 'cluster application status specs', :clusters_applications_ingress
- include_examples 'cluster application helm specs', :clusters_applications_knative
+ include_examples 'cluster application helm specs', :clusters_applications_ingress
before do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index faaabafddb7..a40edbf267b 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
describe Clusters::Applications::Jupyter do
include_examples 'cluster application core specs', :clusters_applications_jupyter
- include_examples 'cluster application helm specs', :clusters_applications_knative
+ include_examples 'cluster application helm specs', :clusters_applications_jupyter
it { is_expected.to belong_to(:oauth_application) }
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index be2a91d566b..d43d88c2924 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -7,6 +7,11 @@ describe Clusters::Applications::Knative do
include_examples 'cluster application status specs', :clusters_applications_knative
include_examples 'cluster application helm specs', :clusters_applications_knative
+ before do
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
+ end
+
describe '.installed' do
subject { described_class.installed }
@@ -45,6 +50,48 @@ describe Clusters::Applications::Knative do
it { is_expected.to contain_exactly(cluster) }
end
+ describe 'make_installed with external_ip' do
+ before do
+ application.make_installed!
+ end
+
+ let(:application) { create(:clusters_applications_knative, :installing) }
+
+ it 'schedules a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).to have_received(:perform_in)
+ .with(Clusters::Applications::Knative::FETCH_IP_ADDRESS_DELAY, 'knative', application.id)
+ end
+ end
+
+ describe '#schedule_status_update with external_ip' do
+ let(:application) { create(:clusters_applications_knative, :installed) }
+
+ before do
+ application.schedule_status_update
+ end
+
+ it 'schedules a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).to have_received(:perform_async)
+ .with('knative', application.id)
+ end
+
+ context 'when the application is not installed' do
+ let(:application) { create(:clusters_applications_knative, :installing) }
+
+ it 'does not schedule a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).not_to have_received(:perform_async)
+ end
+ end
+
+ context 'when there is already an external_ip' do
+ let(:application) { create(:clusters_applications_knative, :installed, external_ip: '111.222.222.111') }
+
+ it 'does not schedule a ClusterWaitForIngressIpAddressWorker' do
+ expect(ClusterWaitForIngressIpAddressWorker).not_to have_received(:perform_in)
+ end
+ end
+ end
+
describe '#install_command' do
subject { knative.install_command }
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index b5aa1dcece5..893ed3e3f64 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -5,7 +5,7 @@ describe Clusters::Applications::Prometheus do
include_examples 'cluster application core specs', :clusters_applications_prometheus
include_examples 'cluster application status specs', :clusters_applications_prometheus
- include_examples 'cluster application helm specs', :clusters_applications_knative
+ include_examples 'cluster application helm specs', :clusters_applications_prometheus
describe '.installed' do
subject { described_class.installed }
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 052cfdbc4b1..97e50809647 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -5,7 +5,7 @@ describe Clusters::Applications::Runner do
include_examples 'cluster application core specs', :clusters_applications_runner
include_examples 'cluster application status specs', :clusters_applications_runner
- include_examples 'cluster application helm specs', :clusters_applications_knative
+ include_examples 'cluster application helm specs', :clusters_applications_runner
it { is_expected.to belong_to(:runner) }
@@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do
let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') }
it 'updates the application version' do
- expect(application.reload.version).to eq('0.1.35')
+ expect(application.reload.version).to eq('0.1.38')
end
end
end
@@ -46,7 +46,7 @@ describe Clusters::Applications::Runner do
it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
- expect(subject.version).to eq('0.1.35')
+ expect(subject.version).to eq('0.1.38')
expect(subject).not_to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
@@ -64,7 +64,7 @@ describe Clusters::Applications::Runner do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.1.35')
+ expect(subject.version).to eq('0.1.38')
end
end
end
@@ -90,7 +90,7 @@ describe Clusters::Applications::Runner do
context 'without a runner' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
- let(:application) { create(:clusters_applications_runner, cluster: cluster) }
+ let(:application) { create(:clusters_applications_runner, runner: nil, cluster: cluster) }
it 'creates a runner' do
expect do
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 98d7e799d67..eb68ebccdcb 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -311,13 +311,14 @@ describe Clusters::Cluster do
context 'when applications are created' do
let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) }
+ let!(:cert_manager) { create(:clusters_applications_cert_managers, cluster: cluster) }
let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
let!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) }
let!(:knative) { create(:clusters_applications_knative, cluster: cluster) }
it 'returns a list of created applications' do
- is_expected.to contain_exactly(helm, ingress, prometheus, runner, jupyter, knative)
+ is_expected.to contain_exactly(helm, ingress, cert_manager, prometheus, runner, jupyter, knative)
end
end
end
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
index 66c1f47d12b..ac8da30b6c9 100644
--- a/spec/models/concerns/relative_positioning_spec.rb
+++ b/spec/models/concerns/relative_positioning_spec.rb
@@ -14,6 +14,14 @@ describe RelativePositioning do
expect(issue.prev_relative_position).to eq nil
expect(issue1.next_relative_position).to eq nil
end
+
+ it 'does not perform any moves if all issues have their relative_position set' do
+ issue.update!(relative_position: 1)
+
+ expect(issue).not_to receive(:save)
+
+ Issue.move_to_end([issue])
+ end
end
describe '#max_relative_position' do
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index a6cc01bea5f..17dc27bd132 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -22,13 +22,13 @@ describe List do
end
describe '#destroy' do
- it 'can be destroyed when when list_type is set to label' do
+ it 'can be destroyed when list_type is set to label' do
subject = create(:list)
expect(subject.destroy).to be_truthy
end
- it 'can not be destroyed when when list_type is set to closed' do
+ it 'can not be destroyed when list_type is set to closed' do
subject = create(:closed_list)
expect(subject.destroy).to be_falsey
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index a58dc8e25e8..ad55c280399 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -30,48 +30,38 @@ describe MergeRequest do
end
describe '#squash_in_progress?' do
- shared_examples 'checking whether a squash is in progress' do
- let(:repo_path) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- subject.source_project.repository.path
- end
- end
- let(:squash_path) { File.join(repo_path, "gitlab-worktree", "squash-#{subject.id}") }
-
- before do
- system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{squash_path} master))
- end
-
- it 'returns true when there is a current squash directory' do
- expect(subject.squash_in_progress?).to be_truthy
+ let(:repo_path) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ subject.source_project.repository.path
end
+ end
+ let(:squash_path) { File.join(repo_path, "gitlab-worktree", "squash-#{subject.id}") }
- it 'returns false when there is no squash directory' do
- FileUtils.rm_rf(squash_path)
+ before do
+ system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{squash_path} master))
+ end
- expect(subject.squash_in_progress?).to be_falsey
- end
+ it 'returns true when there is a current squash directory' do
+ expect(subject.squash_in_progress?).to be_truthy
+ end
- it 'returns false when the squash directory has expired' do
- time = 20.minutes.ago.to_time
- File.utime(time, time, squash_path)
+ it 'returns false when there is no squash directory' do
+ FileUtils.rm_rf(squash_path)
- expect(subject.squash_in_progress?).to be_falsey
- end
+ expect(subject.squash_in_progress?).to be_falsey
+ end
- it 'returns false when the source project has been removed' do
- allow(subject).to receive(:source_project).and_return(nil)
+ it 'returns false when the squash directory has expired' do
+ time = 20.minutes.ago.to_time
+ File.utime(time, time, squash_path)
- expect(subject.squash_in_progress?).to be_falsey
- end
+ expect(subject.squash_in_progress?).to be_falsey
end
- context 'when Gitaly squash_in_progress is enabled' do
- it_behaves_like 'checking whether a squash is in progress'
- end
+ it 'returns false when the source project has been removed' do
+ allow(subject).to receive(:source_project).and_return(nil)
- context 'when Gitaly squash_in_progress is disabled', :disable_gitaly do
- it_behaves_like 'checking whether a squash is in progress'
+ expect(subject.squash_in_progress?).to be_falsey
end
end
@@ -2587,14 +2577,6 @@ describe MergeRequest do
expect(subject.rebase_in_progress?).to be_falsey
end
end
-
- context 'when Gitaly rebase_in_progress is enabled' do
- it_behaves_like 'checking whether a rebase is in progress'
- end
-
- context 'when Gitaly rebase_in_progress is enabled', :disable_gitaly do
- it_behaves_like 'checking whether a rebase is in progress'
- end
end
describe '#allow_collaboration' do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index f9be61e4768..bcdfe3cf1eb 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -517,7 +517,7 @@ describe Note do
describe '#to_ability_name' do
it 'returns snippet for a project snippet note' do
- expect(build(:note_on_project_snippet).to_ability_name).to eq('snippet')
+ expect(build(:note_on_project_snippet).to_ability_name).to eq('project_snippet')
end
it 'returns personal_snippet for a personal snippet note' do
diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb
new file mode 100644
index 00000000000..541e78507e5
--- /dev/null
+++ b/spec/models/pool_repository_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PoolRepository do
+ describe 'associations' do
+ it { is_expected.to belong_to(:shard) }
+ it { is_expected.to have_many(:member_projects) }
+ end
+
+ describe 'validations' do
+ let!(:pool_repository) { create(:pool_repository) }
+
+ it { is_expected.to validate_presence_of(:shard) }
+ end
+
+ describe '#disk_path' do
+ it 'sets the hashed disk_path' do
+ pool = create(:pool_repository)
+
+ elements = File.split(pool.disk_path)
+
+ expect(elements).to all( match(/\d{2,}/) )
+ end
+ end
+end
diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb
index f7033b28c76..e3b2d971419 100644
--- a/spec/models/project_import_state_spec.rb
+++ b/spec/models/project_import_state_spec.rb
@@ -10,4 +10,116 @@ describe ProjectImportState, type: :model do
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
end
+
+ describe 'Project import job' do
+ let(:import_state) { create(:import_state, import_url: generate(:url)) }
+ let(:project) { import_state.project }
+
+ before do
+ allow_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository)
+ .with(project.import_url).and_return(true)
+
+ # Works around https://github.com/rspec/rspec-mocks/issues/910
+ allow(Project).to receive(:find).with(project.id).and_return(project)
+ expect(project.repository).to receive(:after_import).and_call_original
+ expect(project.wiki.repository).to receive(:after_import).and_call_original
+ end
+
+ it 'imports a project' do
+ expect(RepositoryImportWorker).to receive(:perform_async).and_call_original
+
+ expect { import_state.schedule }.to change { import_state.jid }
+ expect(import_state.status).to eq('finished')
+ end
+ end
+
+ describe '#human_status_name' do
+ context 'when import_state exists' do
+ it 'returns the humanized status name' do
+ import_state = build(:import_state, :started)
+
+ expect(import_state.human_status_name).to eq("started")
+ end
+ end
+ end
+
+ describe 'import state transitions' do
+ context 'state transition: [:started] => [:finished]' do
+ let(:after_import_service) { spy(:after_import_service) }
+ let(:housekeeping_service) { spy(:housekeeping_service) }
+
+ before do
+ allow(Projects::AfterImportService)
+ .to receive(:new) { after_import_service }
+
+ allow(after_import_service)
+ .to receive(:execute) { housekeeping_service.execute }
+
+ allow(Projects::HousekeepingService)
+ .to receive(:new) { housekeeping_service }
+ end
+
+ it 'resets last_error' do
+ error_message = 'Some error'
+ import_state = create(:import_state, :started, last_error: error_message)
+
+ expect { import_state.finish }.to change { import_state.last_error }.from(error_message).to(nil)
+ end
+
+ it 'performs housekeeping when an import of a fresh project is completed' do
+ project = create(:project_empty_repo, :import_started, import_type: :github)
+
+ project.import_state.finish
+
+ expect(after_import_service).to have_received(:execute)
+ expect(housekeeping_service).to have_received(:execute)
+ end
+
+ it 'does not perform housekeeping when project repository does not exist' do
+ project = create(:project, :import_started, import_type: :github)
+
+ project.import_state.finish
+
+ expect(housekeeping_service).not_to have_received(:execute)
+ end
+
+ it 'does not perform housekeeping when project does not have a valid import type' do
+ project = create(:project, :import_started, import_type: nil)
+
+ project.import_state.finish
+
+ expect(housekeeping_service).not_to have_received(:execute)
+ end
+ end
+ end
+
+ describe '#remove_jid', :clean_gitlab_redis_cache do
+ let(:project) { }
+
+ context 'without an JID' do
+ it 'does nothing' do
+ import_state = create(:import_state)
+
+ expect(Gitlab::SidekiqStatus)
+ .not_to receive(:unset)
+
+ import_state.remove_jid
+ end
+ end
+
+ context 'with an JID' do
+ it 'unsets the JID' do
+ import_state = create(:import_state, jid: '123')
+
+ expect(Gitlab::SidekiqStatus)
+ .to receive(:unset)
+ .with('123')
+ .and_call_original
+
+ import_state.remove_jid
+
+ expect(import_state.jid).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/project_repository_spec.rb b/spec/models/project_repository_spec.rb
new file mode 100644
index 00000000000..c966447fedc
--- /dev/null
+++ b/spec/models/project_repository_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectRepository do
+ describe 'associations' do
+ it { is_expected.to belong_to(:shard) }
+ it { is_expected.to belong_to(:project) }
+ end
+
+ describe '.find_project' do
+ it 'finds project by disk path' do
+ project = create(:project)
+ project.track_project_repository
+
+ expect(described_class.find_project(project.disk_path)).to eq(project)
+ end
+
+ it 'returns nil when it does not find the project' do
+ expect(described_class.find_project('@@unexisting/path/to/project')).to be_nil
+ end
+ end
+end
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index f2cb927df37..b6cf4c72450 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -13,6 +13,23 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
it { is_expected.to belong_to :project }
end
+ context 'redirects' do
+ it 'does not follow redirects' do
+ redirect_to = 'https://redirected.example.com'
+ redirect_req_stub = stub_prometheus_request(prometheus_query_url('1'), status: 302, headers: { location: redirect_to })
+ redirected_req_stub = stub_prometheus_request(redirect_to, body: { 'status': 'success' })
+
+ result = service.test
+
+ # result = { success: false, result: error }
+ expect(result[:success]).to be_falsy
+ expect(result[:result]).to be_instance_of(Gitlab::PrometheusClient::Error)
+
+ expect(redirect_req_stub).to have_been_requested
+ expect(redirected_req_stub).not_to have_been_requested
+ end
+ end
+
describe 'Validations' do
context 'when manual_configuration is enabled' do
before do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 51278836604..af5b0939ca2 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -54,6 +54,7 @@ describe Project do
it { is_expected.to have_one(:gitlab_issue_tracker_service) }
it { is_expected.to have_one(:external_wiki_service) }
it { is_expected.to have_one(:project_feature) }
+ it { is_expected.to have_one(:project_repository) }
it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') }
it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
it { is_expected.to have_one(:last_event).class_name('Event') }
@@ -109,22 +110,6 @@ describe Project do
end
end
- context 'Site Statistics' do
- context 'when creating a new project' do
- it 'tracks project in SiteStatistic' do
- expect { create(:project) }.to change { SiteStatistic.fetch.repositories_count }.by(1)
- end
- end
-
- context 'when deleting a project' do
- it 'untracks project in SiteStatistic' do
- project = create(:project)
-
- expect { project.destroy }.to change { SiteStatistic.fetch.repositories_count }.by(-1)
- end
- end
- end
-
context 'updating cd_cd_settings' do
it 'does not raise an error' do
project = create(:project)
@@ -234,76 +219,93 @@ describe Project do
end
end
- it 'does not allow an invalid URI as import_url' do
- project = build(:project, import_url: 'invalid://')
+ describe 'import_url' do
+ it 'does not allow an invalid URI as import_url' do
+ project = build(:project, import_url: 'invalid://')
- expect(project).not_to be_valid
- end
+ expect(project).not_to be_valid
+ end
- it 'does allow a SSH URI as import_url for persisted projects' do
- project = create(:project)
- project.import_url = 'ssh://test@gitlab.com/project.git'
+ it 'does allow a SSH URI as import_url for persisted projects' do
+ project = create(:project)
+ project.import_url = 'ssh://test@gitlab.com/project.git'
- expect(project).to be_valid
- end
+ expect(project).to be_valid
+ end
- it 'does not allow a SSH URI as import_url for new projects' do
- project = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
+ it 'does not allow a SSH URI as import_url for new projects' do
+ project = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
- expect(project).not_to be_valid
- end
+ expect(project).not_to be_valid
+ end
- it 'does allow a valid URI as import_url' do
- project = build(:project, import_url: 'http://gitlab.com/project.git')
+ it 'does allow a valid URI as import_url' do
+ project = build(:project, import_url: 'http://gitlab.com/project.git')
- expect(project).to be_valid
- end
+ expect(project).to be_valid
+ end
- it 'allows an empty URI' do
- project = build(:project, import_url: '')
+ it 'allows an empty URI' do
+ project = build(:project, import_url: '')
- expect(project).to be_valid
- end
+ expect(project).to be_valid
+ end
- it 'does not produce import data on an empty URI' do
- project = build(:project, import_url: '')
+ it 'does not produce import data on an empty URI' do
+ project = build(:project, import_url: '')
- expect(project.import_data).to be_nil
- end
+ expect(project.import_data).to be_nil
+ end
- it 'does not produce import data on an invalid URI' do
- project = build(:project, import_url: 'test://')
+ it 'does not produce import data on an invalid URI' do
+ project = build(:project, import_url: 'test://')
- expect(project.import_data).to be_nil
- end
+ expect(project.import_data).to be_nil
+ end
- it "does not allow import_url pointing to localhost" do
- project = build(:project, import_url: 'http://localhost:9000/t.git')
+ it "does not allow import_url pointing to localhost" do
+ project = build(:project, import_url: 'http://localhost:9000/t.git')
- expect(project).to be_invalid
- expect(project.errors[:import_url].first).to include('Requests to localhost are not allowed')
- end
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Requests to localhost are not allowed')
+ end
- it "does not allow import_url with invalid ports for new projects" do
- project = build(:project, import_url: 'http://github.com:25/t.git')
+ it "does not allow import_url with invalid ports for new projects" do
+ project = build(:project, import_url: 'http://github.com:25/t.git')
- expect(project).to be_invalid
- expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443')
- end
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443')
+ end
- it "does not allow import_url with invalid ports for persisted projects" do
- project = create(:project)
- project.import_url = 'http://github.com:25/t.git'
+ it "does not allow import_url with invalid ports for persisted projects" do
+ project = create(:project)
+ project.import_url = 'http://github.com:25/t.git'
- expect(project).to be_invalid
- expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
- end
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
+ end
+
+ it "does not allow import_url with invalid user" do
+ project = build(:project, import_url: 'http://$user:password@github.com/t.git')
+
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
+ end
- it "does not allow import_url with invalid user" do
- project = build(:project, import_url: 'http://$user:password@github.com/t.git')
+ include_context 'invalid urls'
- expect(project).to be_invalid
- expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
+ it 'does not allow urls with CR or LF characters' do
+ project = build(:project)
+
+ aggregate_failures do
+ urls_with_CRLF.each do |url|
+ project.import_url = url
+
+ expect(project).not_to be_valid
+ expect(project.errors.full_messages.first).to match(/is blocked: URI is invalid/)
+ end
+ end
+ end
end
describe 'project pending deletion' do
@@ -1617,6 +1619,30 @@ describe Project do
end
end
+ describe '#track_project_repository' do
+ let(:project) { create(:project, :repository) }
+
+ it 'creates a project_repository' do
+ project.track_project_repository
+
+ expect(project.reload.project_repository).to be_present
+ expect(project.project_repository.disk_path).to eq(project.disk_path)
+ expect(project.project_repository.shard_name).to eq(project.repository_storage)
+ end
+
+ it 'updates the project_repository' do
+ project.track_project_repository
+
+ allow(project).to receive(:disk_path).and_return('@fancy/new/path')
+
+ expect do
+ project.track_project_repository
+ end.not_to change(ProjectRepository, :count)
+
+ expect(project.reload.project_repository.disk_path).to eq(project.disk_path)
+ end
+ end
+
describe '#create_repository' do
let(:project) { create(:project, :repository) }
let(:shell) { Gitlab::Shell.new }
@@ -1702,6 +1728,16 @@ describe Project do
end
end
+ describe 'handling import URL' do
+ it 'returns the sanitized URL' do
+ project = create(:project, :import_started, import_url: 'http://user:pass@test.com')
+
+ project.import_state.finish
+
+ expect(project.reload.import_url).to eq('http://test.com')
+ end
+ end
+
describe '#container_registry_url' do
let(:project) { create(:project) }
@@ -1815,106 +1851,6 @@ describe Project do
end
end
- describe '#human_import_status_name' do
- context 'when import_state exists' do
- it 'returns the humanized status name' do
- project = create(:project)
- create(:import_state, :started, project: project)
-
- expect(project.human_import_status_name).to eq("started")
- end
- end
-
- context 'when import_state was not created yet' do
- let(:project) { create(:project, :import_started) }
-
- it 'ensures import_state is created and returns humanized status name' do
- expect do
- project.human_import_status_name
- end.to change { ProjectImportState.count }.from(0).to(1)
- end
-
- it 'returns humanized status name' do
- expect(project.human_import_status_name).to eq("started")
- end
- end
- end
-
- describe 'Project import job' do
- let(:project) { create(:project, import_url: generate(:url)) }
-
- before do
- allow_any_instance_of(Gitlab::Shell).to receive(:import_repository)
- .with(project.repository_storage, project.disk_path, project.import_url)
- .and_return(true)
-
- # Works around https://github.com/rspec/rspec-mocks/issues/910
- allow(described_class).to receive(:find).with(project.id).and_return(project)
- expect(project.repository).to receive(:after_import)
- .and_call_original
- expect(project.wiki.repository).to receive(:after_import)
- .and_call_original
- end
-
- it 'imports a project' do
- expect_any_instance_of(RepositoryImportWorker).to receive(:perform).and_call_original
-
- expect { project.import_schedule }.to change { project.import_jid }
- expect(project.reload.import_status).to eq('finished')
- end
- end
-
- describe 'project import state transitions' do
- context 'state transition: [:started] => [:finished]' do
- let(:after_import_service) { spy(:after_import_service) }
- let(:housekeeping_service) { spy(:housekeeping_service) }
-
- before do
- allow(Projects::AfterImportService)
- .to receive(:new) { after_import_service }
-
- allow(after_import_service)
- .to receive(:execute) { housekeeping_service.execute }
-
- allow(Projects::HousekeepingService)
- .to receive(:new) { housekeeping_service }
- end
-
- it 'resets project import_error' do
- error_message = 'Some error'
- mirror = create(:project_empty_repo, :import_started)
- mirror.import_state.update(last_error: error_message)
-
- expect { mirror.import_finish }.to change { mirror.import_error }.from(error_message).to(nil)
- end
-
- it 'performs housekeeping when an import of a fresh project is completed' do
- project = create(:project_empty_repo, :import_started, import_type: :github)
-
- project.import_finish
-
- expect(after_import_service).to have_received(:execute)
- expect(housekeeping_service).to have_received(:execute)
- end
-
- it 'does not perform housekeeping when project repository does not exist' do
- project = create(:project, :import_started, import_type: :github)
-
- project.import_finish
-
- expect(housekeeping_service).not_to have_received(:execute)
- end
-
- it 'does not perform housekeeping when project does not have a valid import type' do
- project = create(:project, :import_started, import_type: nil)
-
- project.import_finish
-
- expect(housekeeping_service).not_to have_received(:execute)
- end
- end
- end
-
describe '#latest_successful_builds_for' do
def create_pipeline(status = 'success')
create(:ci_pipeline, project: project,
@@ -1994,6 +1930,42 @@ describe Project do
end
end
+ describe '#import_status' do
+ context 'with import_state' do
+ it 'returns the right status' do
+ project = create(:project, :import_started)
+
+ expect(project.import_status).to eq("started")
+ end
+ end
+
+ context 'without import_state' do
+ it 'returns none' do
+ project = create(:project)
+
+ expect(project.import_status).to eq('none')
+ end
+ end
+ end
+
+ describe '#human_import_status_name' do
+ context 'with import_state' do
+ it 'returns the right human import status' do
+ project = create(:project, :import_started)
+
+ expect(project.human_import_status_name).to eq('started')
+ end
+ end
+
+ context 'without import_state' do
+ it 'returns none' do
+ project = create(:project)
+
+ expect(project.human_import_status_name).to eq('none')
+ end
+ end
+ end
+
describe '#add_import_job' do
let(:import_jid) { '123' }
@@ -2203,12 +2175,6 @@ describe Project do
project.change_head(project.default_branch)
end
- it 'creates the new reference with rugged' do
- expect(project.repository.raw_repository).to receive(:write_ref).with('HEAD', "refs/heads/#{project.default_branch}", shell: false)
-
- project.change_head(project.default_branch)
- end
-
it 'copies the gitattributes' do
expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch)
project.change_head(project.default_branch)
@@ -3436,13 +3402,14 @@ describe Project do
describe '#after_import' do
let(:project) { create(:project) }
+ let(:import_state) { create(:import_state, project: project) }
it 'runs the correct hooks' do
expect(project.repository).to receive(:after_import)
expect(project.wiki.repository).to receive(:after_import)
- expect(project).to receive(:import_finish)
+ expect(import_state).to receive(:finish)
expect(project).to receive(:update_project_counter_caches)
- expect(project).to receive(:remove_import_jid)
+ expect(import_state).to receive(:remove_jid)
expect(project).to receive(:after_create_default_branch)
expect(project).to receive(:refresh_markdown_cache!)
@@ -3452,6 +3419,10 @@ describe Project do
context 'branch protection' do
let(:project) { create(:project, :repository) }
+ before do
+ create(:import_state, :started, project: project)
+ end
+
it 'does not protect when branch protection is disabled' do
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
@@ -3507,37 +3478,6 @@ describe Project do
end
end
- describe '#remove_import_jid', :clean_gitlab_redis_cache do
- let(:project) { }
-
- context 'without an import JID' do
- it 'does nothing' do
- project = create(:project)
-
- expect(Gitlab::SidekiqStatus)
- .not_to receive(:unset)
-
- project.remove_import_jid
- end
- end
-
- context 'with an import JID' do
- it 'unsets the import JID' do
- project = create(:project)
- create(:import_state, project: project, jid: '123')
-
- expect(Gitlab::SidekiqStatus)
- .to receive(:unset)
- .with('123')
- .and_call_original
-
- project.remove_import_jid
-
- expect(project.import_jid).to be_nil
- end
- end
- end
-
describe '#wiki_repository_exists?' do
it 'returns true when the wiki repository exists' do
project = create(:project, :wiki_repo)
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index cc5e34782ec..48a43801b9f 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -130,63 +130,53 @@ describe ProjectWiki do
end
describe "#find_page" do
- shared_examples 'finding a wiki page' do
- before do
- create_page("index page", "This is an awesome Gollum Wiki")
- end
-
- after do
- subject.pages.each { |page| destroy_page(page.page) }
- end
+ before do
+ create_page("index page", "This is an awesome Gollum Wiki")
+ end
- it "returns the latest version of the page if it exists" do
- page = subject.find_page("index page")
- expect(page.title).to eq("index page")
- end
+ after do
+ subject.pages.each { |page| destroy_page(page.page) }
+ end
- it "returns nil if the page does not exist" do
- expect(subject.find_page("non-existent")).to eq(nil)
- end
+ it "returns the latest version of the page if it exists" do
+ page = subject.find_page("index page")
+ expect(page.title).to eq("index page")
+ end
- it "can find a page by slug" do
- page = subject.find_page("index-page")
- expect(page.title).to eq("index page")
- end
+ it "returns nil if the page does not exist" do
+ expect(subject.find_page("non-existent")).to eq(nil)
+ end
- it "returns a WikiPage instance" do
- page = subject.find_page("index page")
- expect(page).to be_a WikiPage
- end
+ it "can find a page by slug" do
+ page = subject.find_page("index-page")
+ expect(page.title).to eq("index page")
+ end
- context 'pages with multibyte-character title' do
- before do
- create_page("autre pagé", "C'est un génial Gollum Wiki")
- end
+ it "returns a WikiPage instance" do
+ page = subject.find_page("index page")
+ expect(page).to be_a WikiPage
+ end
- it "can find a page by slug" do
- page = subject.find_page("autre pagé")
- expect(page.title).to eq("autre pagé")
- end
+ context 'pages with multibyte-character title' do
+ before do
+ create_page("autre pagé", "C'est un génial Gollum Wiki")
end
- context 'pages with invalidly-encoded content' do
- before do
- create_page("encoding is fun", "f\xFCr".b)
- end
-
- it "can find the page" do
- page = subject.find_page("encoding is fun")
- expect(page.content).to eq("fr")
- end
+ it "can find a page by slug" do
+ page = subject.find_page("autre pagé")
+ expect(page.title).to eq("autre pagé")
end
end
- context 'when Gitaly wiki_find_page is enabled' do
- it_behaves_like 'finding a wiki page'
- end
+ context 'pages with invalidly-encoded content' do
+ before do
+ create_page("encoding is fun", "f\xFCr".b)
+ end
- context 'when Gitaly wiki_find_page is disabled', :skip_gitaly_mock do
- it_behaves_like 'finding a wiki page'
+ it "can find the page" do
+ page = subject.find_page("encoding is fun")
+ expect(page.content).to eq("fr")
+ end
end
end
@@ -207,100 +197,80 @@ describe ProjectWiki do
end
describe '#find_file' do
- shared_examples 'finding a wiki file' do
- let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
-
- before do
- subject.wiki # Make sure the wiki repo exists
-
- repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- subject.repository.path_to_repo
- end
-
- BareRepoOperations.new(repo_path).commit_file(image, 'image.png')
- end
+ let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
- it 'returns the latest version of the file if it exists' do
- file = subject.find_file('image.png')
- expect(file.mime_type).to eq('image/png')
- end
+ before do
+ subject.wiki # Make sure the wiki repo exists
- it 'returns nil if the page does not exist' do
- expect(subject.find_file('non-existent')).to eq(nil)
+ repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ subject.repository.path_to_repo
end
- it 'returns a Gitlab::Git::WikiFile instance' do
- file = subject.find_file('image.png')
- expect(file).to be_a Gitlab::Git::WikiFile
- end
+ BareRepoOperations.new(repo_path).commit_file(image, 'image.png')
+ end
- it 'returns the whole file' do
- file = subject.find_file('image.png')
- image.rewind
+ it 'returns the latest version of the file if it exists' do
+ file = subject.find_file('image.png')
+ expect(file.mime_type).to eq('image/png')
+ end
- expect(file.raw_data.b).to eq(image.read.b)
- end
+ it 'returns nil if the page does not exist' do
+ expect(subject.find_file('non-existent')).to eq(nil)
end
- context 'when Gitaly wiki_find_file is enabled' do
- it_behaves_like 'finding a wiki file'
+ it 'returns a Gitlab::Git::WikiFile instance' do
+ file = subject.find_file('image.png')
+ expect(file).to be_a Gitlab::Git::WikiFile
end
- context 'when Gitaly wiki_find_file is disabled', :skip_gitaly_mock do
- it_behaves_like 'finding a wiki file'
+ it 'returns the whole file' do
+ file = subject.find_file('image.png')
+ image.rewind
+
+ expect(file.raw_data.b).to eq(image.read.b)
end
end
describe "#create_page" do
- shared_examples 'creating a wiki page' do
- after do
- destroy_page(subject.pages.first.page)
- end
-
- it "creates a new wiki page" do
- expect(subject.create_page("test page", "this is content")).not_to eq(false)
- expect(subject.pages.count).to eq(1)
- end
-
- it "returns false when a duplicate page exists" do
- subject.create_page("test page", "content")
- expect(subject.create_page("test page", "content")).to eq(false)
- end
-
- it "stores an error message when a duplicate page exists" do
- 2.times { subject.create_page("test page", "content") }
- expect(subject.error_message).to match(/Duplicate page:/)
- end
+ after do
+ destroy_page(subject.pages.first.page)
+ end
- it "sets the correct commit message" do
- subject.create_page("test page", "some content", :markdown, "commit message")
- expect(subject.pages.first.page.version.message).to eq("commit message")
- end
+ it "creates a new wiki page" do
+ expect(subject.create_page("test page", "this is content")).not_to eq(false)
+ expect(subject.pages.count).to eq(1)
+ end
- it 'sets the correct commit email' do
- subject.create_page('test page', 'content')
+ it "returns false when a duplicate page exists" do
+ subject.create_page("test page", "content")
+ expect(subject.create_page("test page", "content")).to eq(false)
+ end
- expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
- expect(commit.committer_email).to eq(user.commit_email)
- end
+ it "stores an error message when a duplicate page exists" do
+ 2.times { subject.create_page("test page", "content") }
+ expect(subject.error_message).to match(/Duplicate page:/)
+ end
- it 'updates project activity' do
- subject.create_page('Test Page', 'This is content')
+ it "sets the correct commit message" do
+ subject.create_page("test page", "some content", :markdown, "commit message")
+ expect(subject.pages.first.page.version.message).to eq("commit message")
+ end
- project.reload
+ it 'sets the correct commit email' do
+ subject.create_page('test page', 'content')
- expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
- expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
- end
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
end
- context 'when Gitaly wiki_write_page is enabled' do
- it_behaves_like 'creating a wiki page'
- end
+ it 'updates project activity' do
+ subject.create_page('Test Page', 'This is content')
- context 'when Gitaly wiki_write_page is disabled', :skip_gitaly_mock do
- it_behaves_like 'creating a wiki page'
+ project.reload
+
+ expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
+ expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
end
end
@@ -351,41 +321,31 @@ describe ProjectWiki do
end
describe "#delete_page" do
- shared_examples 'deleting a wiki page' do
- before do
- create_page("index", "some content")
- @page = subject.wiki.page(title: "index")
- end
-
- it "deletes the page" do
- subject.delete_page(@page)
- expect(subject.pages.count).to eq(0)
- end
-
- it 'sets the correct commit email' do
- subject.delete_page(@page)
-
- expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
- expect(commit.committer_email).to eq(user.commit_email)
- end
+ before do
+ create_page("index", "some content")
+ @page = subject.wiki.page(title: "index")
+ end
- it 'updates project activity' do
- subject.delete_page(@page)
+ it "deletes the page" do
+ subject.delete_page(@page)
+ expect(subject.pages.count).to eq(0)
+ end
- project.reload
+ it 'sets the correct commit email' do
+ subject.delete_page(@page)
- expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
- expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
- end
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
end
- context 'when Gitaly wiki_delete_page is enabled' do
- it_behaves_like 'deleting a wiki page'
- end
+ it 'updates project activity' do
+ subject.delete_page(@page)
+
+ project.reload
- context 'when Gitaly wiki_delete_page is disabled', :skip_gitaly_mock do
- it_behaves_like 'deleting a wiki page'
+ expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
+ expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
end
end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index da61a5f2771..b12ca79847c 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -174,7 +174,15 @@ describe RemoteMirror do
end
context 'with remote mirroring enabled' do
+ it 'defaults to disabling only protected branches' do
+ expect(remote_mirror.only_protected_branches?).to be_falsey
+ end
+
context 'with only protected branches enabled' do
+ before do
+ remote_mirror.only_protected_branches = true
+ end
+
context 'when it did not update in the last minute' do
it 'schedules a RepositoryUpdateRemoteMirrorWorker to run now' do
expect(RepositoryUpdateRemoteMirrorWorker).to receive(:perform_async).with(remote_mirror.id, Time.now)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 56edb0fd6da..f09b4b67061 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -38,49 +38,29 @@ describe Repository do
end
describe '#branch_names_contains' do
- shared_examples '#branch_names_contains' do
- set(:project) { create(:project, :repository) }
- let(:repository) { project.repository }
+ set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
- subject { repository.branch_names_contains(sample_commit.id) }
+ subject { repository.branch_names_contains(sample_commit.id) }
- it { is_expected.to include('master') }
- it { is_expected.not_to include('feature') }
- it { is_expected.not_to include('fix') }
+ it { is_expected.to include('master') }
+ it { is_expected.not_to include('feature') }
+ it { is_expected.not_to include('fix') }
- describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
- expect_to_raise_storage_error do
- broken_repository.branch_names_contains(sample_commit.id)
- end
+ describe 'when storage is broken', :broken_storage do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error do
+ broken_repository.branch_names_contains(sample_commit.id)
end
end
end
-
- context 'when gitaly is enabled' do
- it_behaves_like '#branch_names_contains'
- end
-
- context 'when gitaly is disabled', :skip_gitaly_mock do
- it_behaves_like '#branch_names_contains'
- end
end
describe '#tag_names_contains' do
- shared_examples '#tag_names_contains' do
- subject { repository.tag_names_contains(sample_commit.id) }
-
- it { is_expected.to include('v1.1.0') }
- it { is_expected.not_to include('v1.0.0') }
- end
+ subject { repository.tag_names_contains(sample_commit.id) }
- context 'when gitaly is enabled' do
- it_behaves_like '#tag_names_contains'
- end
-
- context 'when gitaly is enabled', :skip_gitaly_mock do
- it_behaves_like '#tag_names_contains'
- end
+ it { is_expected.to include('v1.1.0') }
+ it { is_expected.not_to include('v1.0.0') }
end
describe 'tags_sorted_by' do
@@ -238,61 +218,41 @@ describe Repository do
end
describe '#last_commit_for_path' do
- shared_examples 'getting last commit for path' do
- subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
+ subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
- it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
+ it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
- describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
- expect_to_raise_storage_error do
- broken_repository.last_commit_id_for_path(sample_commit.id, '.gitignore')
- end
+ describe 'when storage is broken', :broken_storage do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error do
+ broken_repository.last_commit_id_for_path(sample_commit.id, '.gitignore')
end
end
end
-
- context 'when Gitaly feature last_commit_for_path is enabled' do
- it_behaves_like 'getting last commit for path'
- end
-
- context 'when Gitaly feature last_commit_for_path is disabled', :skip_gitaly_mock do
- it_behaves_like 'getting last commit for path'
- end
end
describe '#last_commit_id_for_path' do
- shared_examples 'getting last commit ID for path' do
- subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') }
+ subject { repository.last_commit_id_for_path(sample_commit.id, '.gitignore') }
- it "returns last commit id for a given path" do
- is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8')
- end
+ it "returns last commit id for a given path" do
+ is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8')
+ end
- it "caches last commit id for a given path" do
- cache = repository.send(:cache)
- key = "last_commit_id_for_path:#{sample_commit.id}:#{Digest::SHA1.hexdigest('.gitignore')}"
+ it "caches last commit id for a given path" do
+ cache = repository.send(:cache)
+ key = "last_commit_id_for_path:#{sample_commit.id}:#{Digest::SHA1.hexdigest('.gitignore')}"
- expect(cache).to receive(:fetch).with(key).and_return('c1acaa5')
- is_expected.to eq('c1acaa5')
- end
+ expect(cache).to receive(:fetch).with(key).and_return('c1acaa5')
+ is_expected.to eq('c1acaa5')
+ end
- describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
- expect_to_raise_storage_error do
- broken_repository.last_commit_for_path(sample_commit.id, '.gitignore').id
- end
+ describe 'when storage is broken', :broken_storage do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error do
+ broken_repository.last_commit_for_path(sample_commit.id, '.gitignore').id
end
end
end
-
- context 'when Gitaly feature last_commit_for_path is enabled' do
- it_behaves_like 'getting last commit ID for path'
- end
-
- context 'when Gitaly feature last_commit_for_path is disabled', :skip_gitaly_mock do
- it_behaves_like 'getting last commit ID for path'
- end
end
describe '#commits' do
@@ -374,78 +334,57 @@ describe Repository do
describe '#commits_by' do
set(:project) { create(:project, :repository) }
+ let(:oids) { TestEnv::BRANCH_SHA.values }
- shared_examples 'batch commits fetching' do
- let(:oids) { TestEnv::BRANCH_SHA.values }
+ subject { project.repository.commits_by(oids: oids) }
- subject { project.repository.commits_by(oids: oids) }
+ it 'finds each commit' do
+ expect(subject).not_to include(nil)
+ expect(subject.size).to eq(oids.size)
+ end
- it 'finds each commit' do
- expect(subject).not_to include(nil)
- expect(subject.size).to eq(oids.size)
- end
+ it 'returns only Commit instances' do
+ expect(subject).to all( be_a(Commit) )
+ end
- it 'returns only Commit instances' do
- expect(subject).to all( be_a(Commit) )
+ context 'when some commits are not found ' do
+ let(:oids) do
+ ['deadbeef'] + TestEnv::BRANCH_SHA.values.first(10)
end
- context 'when some commits are not found ' do
- let(:oids) do
- ['deadbeef'] + TestEnv::BRANCH_SHA.values.first(10)
- end
-
- it 'returns only found commits' do
- expect(subject).not_to include(nil)
- expect(subject.size).to eq(10)
- end
+ it 'returns only found commits' do
+ expect(subject).not_to include(nil)
+ expect(subject.size).to eq(10)
end
+ end
- context 'when no oids are passed' do
- let(:oids) { [] }
+ context 'when no oids are passed' do
+ let(:oids) { [] }
- it 'does not call #batch_by_oid' do
- expect(Gitlab::Git::Commit).not_to receive(:batch_by_oid)
+ it 'does not call #batch_by_oid' do
+ expect(Gitlab::Git::Commit).not_to receive(:batch_by_oid)
- subject
- end
+ subject
end
end
-
- context 'when Gitaly list_commits_by_oid is enabled' do
- it_behaves_like 'batch commits fetching'
- end
-
- context 'when Gitaly list_commits_by_oid is enabled', :disable_gitaly do
- it_behaves_like 'batch commits fetching'
- end
end
describe '#find_commits_by_message' do
- shared_examples 'finding commits by message' do
- it 'returns commits with messages containing a given string' do
- commit_ids = repository.find_commits_by_message('submodule').map(&:id)
-
- expect(commit_ids).to include(
- '5937ac0a7beb003549fc5fd26fc247adbce4a52e',
- '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9',
- 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660'
- )
- expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e')
- end
-
- it 'is case insensitive' do
- commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id)
+ it 'returns commits with messages containing a given string' do
+ commit_ids = repository.find_commits_by_message('submodule').map(&:id)
- expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
- end
+ expect(commit_ids).to include(
+ '5937ac0a7beb003549fc5fd26fc247adbce4a52e',
+ '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9',
+ 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660'
+ )
+ expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e')
end
- context 'when Gitaly commits_by_message feature is enabled' do
- it_behaves_like 'finding commits by message'
- end
+ it 'is case insensitive' do
+ commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id)
- context 'when Gitaly commits_by_message feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'finding commits by message'
+ expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
end
describe 'when storage is broken', :broken_storage do
@@ -1328,34 +1267,23 @@ describe Repository do
describe '#merge' do
let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) }
-
let(:message) { 'Test \r\n\r\n message' }
- shared_examples '#merge' do
- it 'merges the code and returns the commit id' do
- expect(merge_commit).to be_present
- expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present
- end
-
- it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do
- merge_commit_id = merge(repository, user, merge_request, message)
-
- expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id)
- end
+ it 'merges the code and returns the commit id' do
+ expect(merge_commit).to be_present
+ expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present
+ end
- it 'removes carriage returns from commit message' do
- merge_commit_id = merge(repository, user, merge_request, message)
+ it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do
+ merge_commit_id = merge(repository, user, merge_request, message)
- expect(repository.commit(merge_commit_id).message).to eq(message.delete("\r"))
- end
+ expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id)
end
- context 'with gitaly' do
- it_behaves_like '#merge'
- end
+ it 'removes carriage returns from commit message' do
+ merge_commit_id = merge(repository, user, merge_request, message)
- context 'without gitaly', :skip_gitaly_mock do
- it_behaves_like '#merge'
+ expect(repository.commit(merge_commit_id).message).to eq(message.delete("\r"))
end
def merge(repository, user, merge_request, message)
@@ -1392,98 +1320,78 @@ describe Repository do
end
describe '#revert' do
- shared_examples 'reverting a commit' do
- let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') }
- let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
- let(:message) { 'revert message' }
-
- context 'when there is a conflict' do
- it 'raises an error' do
- expect { repository.revert(user, new_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
- end
- end
-
- context 'when commit was already reverted' do
- it 'raises an error' do
- repository.revert(user, update_image_commit, 'master', message)
-
- expect { repository.revert(user, update_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
- end
- end
+ let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') }
+ let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
+ let(:message) { 'revert message' }
- context 'when commit can be reverted' do
- it 'reverts the changes' do
- expect(repository.revert(user, update_image_commit, 'master', message)).to be_truthy
- end
+ context 'when there is a conflict' do
+ it 'raises an error' do
+ expect { repository.revert(user, new_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
end
+ end
- context 'reverting a merge commit' do
- it 'reverts the changes' do
- merge_commit
- expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).to be_present
+ context 'when commit was already reverted' do
+ it 'raises an error' do
+ repository.revert(user, update_image_commit, 'master', message)
- repository.revert(user, merge_commit, 'master', message)
- expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present
- end
+ expect { repository.revert(user, update_image_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
end
end
- context 'when Gitaly revert feature is enabled' do
- it_behaves_like 'reverting a commit'
+ context 'when commit can be reverted' do
+ it 'reverts the changes' do
+ expect(repository.revert(user, update_image_commit, 'master', message)).to be_truthy
+ end
end
- context 'when Gitaly revert feature is disabled', :disable_gitaly do
- it_behaves_like 'reverting a commit'
+ context 'reverting a merge commit' do
+ it 'reverts the changes' do
+ merge_commit
+ expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).to be_present
+
+ repository.revert(user, merge_commit, 'master', message)
+ expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present
+ end
end
end
describe '#cherry_pick' do
- shared_examples 'cherry-picking a commit' do
- let(:conflict_commit) { repository.commit('c642fe9b8b9f28f9225d7ea953fe14e74748d53b') }
- let(:pickable_commit) { repository.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
- let(:pickable_merge) { repository.commit('e56497bb5f03a90a51293fc6d516788730953899') }
- let(:message) { 'cherry-pick message' }
-
- context 'when there is a conflict' do
- it 'raises an error' do
- expect { repository.cherry_pick(user, conflict_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
- end
+ let(:conflict_commit) { repository.commit('c642fe9b8b9f28f9225d7ea953fe14e74748d53b') }
+ let(:pickable_commit) { repository.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
+ let(:pickable_merge) { repository.commit('e56497bb5f03a90a51293fc6d516788730953899') }
+ let(:message) { 'cherry-pick message' }
+
+ context 'when there is a conflict' do
+ it 'raises an error' do
+ expect { repository.cherry_pick(user, conflict_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
end
+ end
- context 'when commit was already cherry-picked' do
- it 'raises an error' do
- repository.cherry_pick(user, pickable_commit, 'master', message)
+ context 'when commit was already cherry-picked' do
+ it 'raises an error' do
+ repository.cherry_pick(user, pickable_commit, 'master', message)
- expect { repository.cherry_pick(user, pickable_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
- end
+ expect { repository.cherry_pick(user, pickable_commit, 'master', message) }.to raise_error(Gitlab::Git::Repository::CreateTreeError)
end
+ end
- context 'when commit can be cherry-picked' do
- it 'cherry-picks the changes' do
- expect(repository.cherry_pick(user, pickable_commit, 'master', message)).to be_truthy
- end
+ context 'when commit can be cherry-picked' do
+ it 'cherry-picks the changes' do
+ expect(repository.cherry_pick(user, pickable_commit, 'master', message)).to be_truthy
end
+ end
- context 'cherry-picking a merge commit' do
- it 'cherry-picks the changes' do
- expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).to be_nil
+ context 'cherry-picking a merge commit' do
+ it 'cherry-picks the changes' do
+ expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).to be_nil
- cherry_pick_commit_sha = repository.cherry_pick(user, pickable_merge, 'improve/awesome', message)
- cherry_pick_commit_message = project.commit(cherry_pick_commit_sha).message
+ cherry_pick_commit_sha = repository.cherry_pick(user, pickable_merge, 'improve/awesome', message)
+ cherry_pick_commit_message = project.commit(cherry_pick_commit_sha).message
- expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).not_to be_nil
- expect(cherry_pick_commit_message).to eq(message)
- end
+ expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).not_to be_nil
+ expect(cherry_pick_commit_message).to eq(message)
end
end
-
- context 'when Gitaly cherry_pick feature is enabled' do
- it_behaves_like 'cherry-picking a commit'
- end
-
- context 'when Gitaly cherry_pick feature is disabled', :disable_gitaly do
- it_behaves_like 'cherry-picking a commit'
- end
end
describe '#before_delete' do
@@ -1580,6 +1488,7 @@ describe Repository do
:size,
:commit_count,
:rendered_readme,
+ :readme_path,
:contribution_guide,
:changelog,
:license_blob,
@@ -1966,6 +1875,42 @@ describe Repository do
end
end
+ describe '#readme_path', :use_clean_rails_memory_store_caching do
+ context 'with a non-existing repository' do
+ let(:project) { create(:project) }
+
+ it 'returns nil' do
+ expect(repository.readme_path).to be_nil
+ end
+ end
+
+ context 'with an existing repository' do
+ context 'when no README exists' do
+ let(:project) { create(:project, :empty_repo) }
+
+ it 'returns nil' do
+ expect(repository.readme_path).to be_nil
+ end
+ end
+
+ context 'when a README exists' do
+ let(:project) { create(:project, :repository) }
+
+ it 'returns the README' do
+ expect(repository.readme_path).to eq("README.md")
+ end
+
+ it 'caches the response' do
+ expect(repository).to receive(:readme).and_call_original.once
+
+ 2.times do
+ expect(repository.readme_path).to eq("README.md")
+ end
+ end
+ end
+ end
+ end
+
describe '#expire_statistics_caches' do
it 'expires the caches' do
expect(repository).to receive(:expire_method_caches)
@@ -2134,9 +2079,10 @@ describe Repository do
describe '#refresh_method_caches' do
it 'refreshes the caches of the given types' do
expect(repository).to receive(:expire_method_caches)
- .with(%i(rendered_readme license_blob license_key license))
+ .with(%i(rendered_readme readme_path license_blob license_key license))
expect(repository).to receive(:rendered_readme)
+ expect(repository).to receive(:readme_path)
expect(repository).to receive(:license_blob)
expect(repository).to receive(:license_key)
expect(repository).to receive(:license)
@@ -2190,33 +2136,23 @@ describe Repository do
let(:commit) { repository.commit }
let(:ancestor) { commit.parents.first }
- shared_examples '#ancestor?' do
- it 'it is an ancestor' do
- expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
- end
-
- it 'it is not an ancestor' do
- expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false)
- end
-
- it 'returns false on nil-values' do
- expect(repository.ancestor?(nil, commit.id)).to eq(false)
- expect(repository.ancestor?(ancestor.id, nil)).to eq(false)
- expect(repository.ancestor?(nil, nil)).to eq(false)
- end
+ it 'it is an ancestor' do
+ expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
+ end
- it 'returns false for invalid commit IDs' do
- expect(repository.ancestor?(commit.id, Gitlab::Git::BLANK_SHA)).to eq(false)
- expect(repository.ancestor?( Gitlab::Git::BLANK_SHA, commit.id)).to eq(false)
- end
+ it 'it is not an ancestor' do
+ expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false)
end
- context 'with Gitaly enabled' do
- it_behaves_like('#ancestor?')
+ it 'returns false on nil-values' do
+ expect(repository.ancestor?(nil, commit.id)).to eq(false)
+ expect(repository.ancestor?(ancestor.id, nil)).to eq(false)
+ expect(repository.ancestor?(nil, nil)).to eq(false)
end
- context 'with Gitaly disabled', :skip_gitaly_mock do
- it_behaves_like('#ancestor?')
+ it 'returns false for invalid commit IDs' do
+ expect(repository.ancestor?(commit.id, Gitlab::Git::BLANK_SHA)).to eq(false)
+ expect(repository.ancestor?( Gitlab::Git::BLANK_SHA, commit.id)).to eq(false)
end
end
diff --git a/spec/models/site_statistic_spec.rb b/spec/models/site_statistic_spec.rb
deleted file mode 100644
index 0e739900065..00000000000
--- a/spec/models/site_statistic_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-require 'spec_helper'
-
-describe SiteStatistic do
- describe '.fetch' do
- context 'existing record' do
- it 'returns existing SiteStatistic model' do
- statistics = create(:site_statistics)
-
- expect(described_class.fetch).to be_a(described_class)
- expect(described_class.fetch).to eq(statistics)
- end
- end
-
- context 'non existing record' do
- it 'creates a new SiteStatistic model' do
- expect(described_class.first).to be_nil
- expect(described_class.fetch).to be_a(described_class)
- end
- end
- end
-
- describe '.track' do
- context 'with allowed attributes' do
- let(:statistics) { create(:site_statistics) }
-
- it 'increases the attribute counter' do
- expect { described_class.track('repositories_count') }.to change { statistics.reload.repositories_count }.by(1)
- end
-
- it 'doesnt increase the attribute counter when an exception happens during transaction' do
- expect do
- begin
- described_class.transaction do
- described_class.track('repositories_count')
-
- raise StandardError
- end
- rescue StandardError
- # no-op
- end
- end.not_to change { statistics.reload.repositories_count }
- end
- end
-
- context 'with not allowed attributes' do
- it 'returns error' do
- expect { described_class.track('something_else') }.to raise_error(ArgumentError).with_message(/Invalid attribute: \'something_else\' to \'track\' method/)
- end
- end
- end
-
- describe '.untrack' do
- context 'with allowed attributes' do
- let(:statistics) { create(:site_statistics) }
-
- it 'decreases the attribute counter' do
- expect { described_class.untrack('repositories_count') }.to change { statistics.reload.repositories_count }.by(-1)
- end
-
- it 'doesnt decrease the attribute counter when an exception happens during transaction' do
- expect do
- begin
- described_class.transaction do
- described_class.track('repositories_count')
-
- raise StandardError
- end
- rescue StandardError
- # no-op
- end
- end.not_to change { described_class.fetch.repositories_count }
- end
- end
-
- context 'with not allowed attributes' do
- it 'returns error' do
- expect { described_class.untrack('something_else') }.to raise_error(ArgumentError).with_message(/Invalid attribute: \'something_else\' to \'untrack\' method/)
- end
- end
- end
-end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 733c1c49f08..7bd6dccd0ad 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1137,12 +1137,38 @@ describe User do
expect(described_class.find_by_any_email(user.email.upcase, confirmed: true)).to eq user
end
- it 'finds by secondary email' do
- email = create(:email, email: 'foo@example.com')
- user = email.user
+ context 'finds by secondary email' do
+ let(:user) { email.user }
- expect(described_class.find_by_any_email(email.email)).to eq user
- expect(described_class.find_by_any_email(email.email, confirmed: true)).to eq user
+ context 'primary email confirmed' do
+ context 'secondary email confirmed' do
+ let!(:email) { create(:email, :confirmed, email: 'foo@example.com') }
+
+ it 'finds user respecting the confirmed flag' do
+ expect(described_class.find_by_any_email(email.email)).to eq user
+ expect(described_class.find_by_any_email(email.email, confirmed: true)).to eq user
+ end
+ end
+
+ context 'secondary email not confirmed' do
+ let!(:email) { create(:email, email: 'foo@example.com') }
+
+ it 'finds user respecting the confirmed flag' do
+ expect(described_class.find_by_any_email(email.email)).to eq user
+ expect(described_class.find_by_any_email(email.email, confirmed: true)).to be_nil
+ end
+ end
+ end
+
+ context 'primary email not confirmed' do
+ let(:user) { create(:user, confirmed_at: nil) }
+ let!(:email) { create(:email, :confirmed, user: user, email: 'foo@example.com') }
+
+ it 'finds user respecting the confirmed flag' do
+ expect(described_class.find_by_any_email(email.email)).to eq user
+ expect(described_class.find_by_any_email(email.email, confirmed: true)).to be_nil
+ end
+ end
end
it 'returns nil when nothing found' do
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index b87a2d871e5..cba22b2cc4e 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -200,180 +200,160 @@ describe WikiPage do
end
describe '#create' do
- shared_examples 'create method' do
- context 'with valid attributes' do
- it 'raises an error if a page with the same path already exists' do
- create_page('New Page', 'content')
- create_page('foo/bar', 'content')
- expect { create_page('New Page', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError
- expect { create_page('foo/bar', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError
-
- destroy_page('New Page')
- destroy_page('bar', 'foo')
- end
+ context 'with valid attributes' do
+ it 'raises an error if a page with the same path already exists' do
+ create_page('New Page', 'content')
+ create_page('foo/bar', 'content')
+ expect { create_page('New Page', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError
+ expect { create_page('foo/bar', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError
- it 'if the title is preceded by a / it is removed' do
- create_page('/New Page', 'content')
+ destroy_page('New Page')
+ destroy_page('bar', 'foo')
+ end
- expect(wiki.find_page('New Page')).not_to be_nil
+ it 'if the title is preceded by a / it is removed' do
+ create_page('/New Page', 'content')
- destroy_page('New Page')
- end
+ expect(wiki.find_page('New Page')).not_to be_nil
+
+ destroy_page('New Page')
end
end
+ end
- context 'when Gitaly is enabled' do
- it_behaves_like 'create method'
+ describe "#update" do
+ before do
+ create_page("Update", "content")
+ @page = wiki.find_page("Update")
end
- context 'when Gitaly is disabled', :skip_gitaly_mock do
- it_behaves_like 'create method'
+ after do
+ destroy_page(@page.title, @page.directory)
end
- end
- describe "#update" do
- shared_examples 'update method' do
- before do
- create_page("Update", "content")
+ context "with valid attributes" do
+ it "updates the content of the page" do
+ new_content = "new content"
+
+ @page.update(content: new_content)
@page = wiki.find_page("Update")
- end
- after do
- destroy_page(@page.title, @page.directory)
+ expect(@page.content).to eq("new content")
end
- context "with valid attributes" do
- it "updates the content of the page" do
- new_content = "new content"
-
- @page.update(content: new_content)
- @page = wiki.find_page("Update")
-
- expect(@page.content).to eq("new content")
- end
+ it "updates the title of the page" do
+ new_title = "Index v.1.2.4"
- it "updates the title of the page" do
- new_title = "Index v.1.2.4"
+ @page.update(title: new_title)
+ @page = wiki.find_page(new_title)
- @page.update(title: new_title)
- @page = wiki.find_page(new_title)
-
- expect(@page.title).to eq(new_title)
- end
+ expect(@page.title).to eq(new_title)
+ end
- it "returns true" do
- expect(@page.update(content: "more content")).to be_truthy
- end
+ it "returns true" do
+ expect(@page.update(content: "more content")).to be_truthy
end
+ end
- context 'with same last commit sha' do
- it 'returns true' do
- expect(@page.update(content: 'more content', last_commit_sha: @page.last_commit_sha)).to be_truthy
- end
+ context 'with same last commit sha' do
+ it 'returns true' do
+ expect(@page.update(content: 'more content', last_commit_sha: @page.last_commit_sha)).to be_truthy
end
+ end
- context 'with different last commit sha' do
- it 'raises exception' do
- expect { @page.update(content: 'more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError)
- end
+ context 'with different last commit sha' do
+ it 'raises exception' do
+ expect { @page.update(content: 'more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError)
end
+ end
- context 'when renaming a page' do
- it 'raises an error if the page already exists' do
- create_page('Existing Page', 'content')
+ context 'when renaming a page' do
+ it 'raises an error if the page already exists' do
+ create_page('Existing Page', 'content')
- expect { @page.update(title: 'Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError)
- expect(@page.title).to eq 'Update'
- expect(@page.content).to eq 'new_content'
+ expect { @page.update(title: 'Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError)
+ expect(@page.title).to eq 'Update'
+ expect(@page.content).to eq 'new_content'
- destroy_page('Existing Page')
- end
+ destroy_page('Existing Page')
+ end
- it 'updates the content and rename the file' do
- new_title = 'Renamed Page'
- new_content = 'updated content'
+ it 'updates the content and rename the file' do
+ new_title = 'Renamed Page'
+ new_content = 'updated content'
- expect(@page.update(title: new_title, content: new_content)).to be_truthy
+ expect(@page.update(title: new_title, content: new_content)).to be_truthy
- @page = wiki.find_page(new_title)
+ @page = wiki.find_page(new_title)
- expect(@page).not_to be_nil
- expect(@page.content).to eq new_content
- end
+ expect(@page).not_to be_nil
+ expect(@page.content).to eq new_content
end
+ end
- context 'when moving a page' do
- it 'raises an error if the page already exists' do
- create_page('foo/Existing Page', 'content')
-
- expect { @page.update(title: 'foo/Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError)
- expect(@page.title).to eq 'Update'
- expect(@page.content).to eq 'new_content'
+ context 'when moving a page' do
+ it 'raises an error if the page already exists' do
+ create_page('foo/Existing Page', 'content')
- destroy_page('Existing Page', 'foo')
- end
+ expect { @page.update(title: 'foo/Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError)
+ expect(@page.title).to eq 'Update'
+ expect(@page.content).to eq 'new_content'
- it 'updates the content and moves the file' do
- new_title = 'foo/Other Page'
- new_content = 'new_content'
-
- expect(@page.update(title: new_title, content: new_content)).to be_truthy
+ destroy_page('Existing Page', 'foo')
+ end
- page = wiki.find_page(new_title)
+ it 'updates the content and moves the file' do
+ new_title = 'foo/Other Page'
+ new_content = 'new_content'
- expect(page).not_to be_nil
- expect(page.content).to eq new_content
- end
+ expect(@page.update(title: new_title, content: new_content)).to be_truthy
- context 'in subdir' do
- before do
- create_page('foo/Existing Page', 'content')
- @page = wiki.find_page('foo/Existing Page')
- end
+ page = wiki.find_page(new_title)
- it 'moves the page to the root folder if the title is preceded by /', :skip_gitaly_mock do
- expect(@page.slug).to eq 'foo/Existing-Page'
- expect(@page.update(title: '/Existing Page', content: 'new_content')).to be_truthy
- expect(@page.slug).to eq 'Existing-Page'
- end
+ expect(page).not_to be_nil
+ expect(page.content).to eq new_content
+ end
- it 'does nothing if it has the same title' do
- original_path = @page.slug
+ context 'in subdir' do
+ before do
+ create_page('foo/Existing Page', 'content')
+ @page = wiki.find_page('foo/Existing Page')
+ end
- expect(@page.update(title: 'Existing Page', content: 'new_content')).to be_truthy
- expect(@page.slug).to eq original_path
- end
+ it 'moves the page to the root folder if the title is preceded by /' do
+ expect(@page.slug).to eq 'foo/Existing-Page'
+ expect(@page.update(title: '/Existing Page', content: 'new_content')).to be_truthy
+ expect(@page.slug).to eq 'Existing-Page'
end
- context 'in root dir' do
- it 'does nothing if the title is preceded by /' do
- original_path = @page.slug
+ it 'does nothing if it has the same title' do
+ original_path = @page.slug
- expect(@page.update(title: '/Update', content: 'new_content')).to be_truthy
- expect(@page.slug).to eq original_path
- end
+ expect(@page.update(title: 'Existing Page', content: 'new_content')).to be_truthy
+ expect(@page.slug).to eq original_path
end
end
- context "with invalid attributes" do
- it 'aborts update if title blank' do
- expect(@page.update(title: '', content: 'new_content')).to be_falsey
- expect(@page.content).to eq 'new_content'
+ context 'in root dir' do
+ it 'does nothing if the title is preceded by /' do
+ original_path = @page.slug
- page = wiki.find_page('Update')
- expect(page.content).to eq 'content'
-
- @page.title = 'Update'
+ expect(@page.update(title: '/Update', content: 'new_content')).to be_truthy
+ expect(@page.slug).to eq original_path
end
end
end
- context 'when Gitaly is enabled' do
- it_behaves_like 'update method'
- end
+ context "with invalid attributes" do
+ it 'aborts update if title blank' do
+ expect(@page.update(title: '', content: 'new_content')).to be_falsey
+ expect(@page.content).to eq 'new_content'
- context 'when Gitaly is disabled', :skip_gitaly_mock do
- it_behaves_like 'update method'
+ page = wiki.find_page('Update')
+ expect(page.content).to eq 'content'
+
+ @page.title = 'Update'
+ end
end
end
@@ -394,34 +374,24 @@ describe WikiPage do
end
describe "#versions" do
- shared_examples 'wiki page versions' do
- let(:page) { wiki.find_page("Update") }
+ let(:page) { wiki.find_page("Update") }
- before do
- create_page("Update", "content")
- end
-
- after do
- destroy_page("Update")
- end
-
- it "returns an array of all commits for the page" do
- 3.times { |i| page.update(content: "content #{i}") }
-
- expect(page.versions.count).to eq(4)
- end
+ before do
+ create_page("Update", "content")
+ end
- it 'returns instances of WikiPageVersion' do
- expect(page.versions).to all( be_a(Gitlab::Git::WikiPageVersion) )
- end
+ after do
+ destroy_page("Update")
end
- context 'when Gitaly is enabled' do
- it_behaves_like 'wiki page versions'
+ it "returns an array of all commits for the page" do
+ 3.times { |i| page.update(content: "content #{i}") }
+
+ expect(page.versions.count).to eq(4)
end
- context 'when Gitaly is disabled', :disable_gitaly do
- it_behaves_like 'wiki page versions'
+ it 'returns instances of WikiPageVersion' do
+ expect(page.versions).to all( be_a(Gitlab::Git::WikiPageVersion) )
end
end
@@ -555,23 +525,13 @@ describe WikiPage do
end
describe '#formatted_content' do
- shared_examples 'fetching page formatted content' do
- it 'returns processed content of the page' do
- subject.create({ title: "RDoc", content: "*bold*", format: "rdoc" })
- page = wiki.find_page('RDoc')
-
- expect(page.formatted_content).to eq("\n<p><strong>bold</strong></p>\n")
+ it 'returns processed content of the page' do
+ subject.create({ title: "RDoc", content: "*bold*", format: "rdoc" })
+ page = wiki.find_page('RDoc')
- destroy_page('RDoc')
- end
- end
-
- context 'when Gitaly wiki_page_formatted_data is enabled' do
- it_behaves_like 'fetching page formatted content'
- end
+ expect(page.formatted_content).to eq("\n<p><strong>bold</strong></p>\n")
- context 'when Gitaly wiki_page_formatted_data is disabled', :disable_gitaly do
- it_behaves_like 'fetching page formatted content'
+ destroy_page('RDoc')
end
end
diff --git a/spec/policies/ci/pipeline_schedule_policy_spec.rb b/spec/policies/ci/pipeline_schedule_policy_spec.rb
index f1d3cd04e32..5a56e91cd69 100644
--- a/spec/policies/ci/pipeline_schedule_policy_spec.rb
+++ b/spec/policies/ci/pipeline_schedule_policy_spec.rb
@@ -70,7 +70,7 @@ describe Ci::PipelineSchedulePolicy, :models do
pipeline_schedule.update(owner: user)
end
- it 'includes abilities to do do all operations on pipeline schedule' do
+ it 'includes abilities to do all operations on pipeline schedule' do
expect(policy).to be_allowed :play_pipeline_schedule
expect(policy).to be_allowed :update_pipeline_schedule
expect(policy).to be_allowed :admin_pipeline_schedule
@@ -82,7 +82,7 @@ describe Ci::PipelineSchedulePolicy, :models do
project.add_maintainer(user)
end
- it 'includes abilities to do do all operations on pipeline schedule' do
+ it 'includes abilities to do all operations on pipeline schedule' do
expect(policy).to be_allowed :play_pipeline_schedule
expect(policy).to be_allowed :update_pipeline_schedule
expect(policy).to be_allowed :admin_pipeline_schedule
diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb
index e8096358f7d..7e25c53e77c 100644
--- a/spec/policies/note_policy_spec.rb
+++ b/spec/policies/note_policy_spec.rb
@@ -10,11 +10,50 @@ describe NotePolicy, mdoels: true do
return @policies if @policies
noteable ||= issue
- note = create(:note, noteable: noteable, author: user, project: project)
+ note = if noteable.is_a?(Commit)
+ create(:note_on_commit, commit_id: noteable.id, author: user, project: project)
+ else
+ create(:note, noteable: noteable, author: user, project: project)
+ end
@policies = described_class.new(user, note)
end
+ shared_examples_for 'a discussion with a private noteable' do
+ let(:noteable) { issue }
+ let(:policy) { policies(noteable) }
+
+ context 'when the note author can no longer see the noteable' do
+ it 'can not edit nor read the note' do
+ expect(policy).to be_disallowed(:admin_note)
+ expect(policy).to be_disallowed(:resolve_note)
+ expect(policy).to be_disallowed(:read_note)
+ end
+ end
+
+ context 'when the note author can still see the noteable' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'can edit the note' do
+ expect(policy).to be_allowed(:admin_note)
+ expect(policy).to be_allowed(:resolve_note)
+ expect(policy).to be_allowed(:read_note)
+ end
+ end
+ end
+
+ context 'when the project is private' do
+ let(:project) { create(:project, :private, :repository) }
+
+ context 'when the noteable is a commit' do
+ it_behaves_like 'a discussion with a private noteable' do
+ let(:noteable) { project.repository.head_commit }
+ end
+ end
+ end
+
context 'when the project is public' do
context 'when the note author is not a project member' do
it 'can edit a note' do
@@ -24,14 +63,48 @@ describe NotePolicy, mdoels: true do
end
end
- context 'when the noteable is a snippet' do
+ context 'when the noteable is a project snippet' do
+ it 'can edit note' do
+ policies = policies(create(:project_snippet, :public, project: project))
+
+ expect(policies).to be_allowed(:admin_note)
+ expect(policies).to be_allowed(:resolve_note)
+ expect(policies).to be_allowed(:read_note)
+ end
+
+ context 'when it is private' do
+ it_behaves_like 'a discussion with a private noteable' do
+ let(:noteable) { create(:project_snippet, :private, project: project) }
+ end
+ end
+ end
+
+ context 'when the noteable is a personal snippet' do
it 'can edit note' do
- policies = policies(create(:project_snippet, project: project))
+ policies = policies(create(:personal_snippet, :public))
expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note)
end
+
+ context 'when it is private' do
+ it 'can not edit nor read the note' do
+ policies = policies(create(:personal_snippet, :private))
+
+ expect(policies).to be_disallowed(:admin_note)
+ expect(policies).to be_disallowed(:resolve_note)
+ expect(policies).to be_disallowed(:read_note)
+ end
+ end
+ end
+
+ context 'when a discussion is confidential' do
+ before do
+ issue.update_attribute(:confidential, true)
+ end
+
+ it_behaves_like 'a discussion with a private noteable'
end
context 'when a discussion is locked' do
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index d6bc67a9d70..69468f9ad85 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -223,7 +223,7 @@ describe ProjectPolicy do
expect_disallowed(*other_write_abilities)
end
- it 'does not disable other other abilities' do
+ it 'does not disable other abilities' do
expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities))
end
end
diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb
index 270e12bf201..6154be5c425 100644
--- a/spec/requests/api/applications_spec.rb
+++ b/spec/requests/api/applications_spec.rb
@@ -25,7 +25,7 @@ describe API::Applications, :api do
it 'does not allow creating an application with the wrong redirect_uri format' do
expect do
- post api('/applications', admin_user), name: 'application_name', redirect_uri: 'wrong_url_format', scopes: ''
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://', scopes: ''
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(400)
@@ -33,6 +33,16 @@ describe API::Applications, :api do
expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.')
end
+ it 'does not allow creating an application with a forbidden URI format' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'javascript://alert()', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response).to be_a Hash
+ expect(json_response['message']['redirect_uri'][0]).to eq('is forbidden by the server.')
+ end
+
it 'does not allow creating an application without a name' do
expect do
post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: ''
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index a2b41d56b8b..334dbb1c34c 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -178,6 +178,14 @@ describe API::Files do
expect(response).to have_gitlab_http_status(200)
end
+ it 'forces attachment content disposition' do
+ url = route(file_path) + "/raw"
+
+ get api(url, current_user), params
+
+ expect(headers['Content-Disposition']).to match(/^attachment/)
+ end
+
context 'when mandatory params are not given' do
it_behaves_like '400 response' do
let(:request) { get api(route("any%2Ffile"), current_user) }
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
new file mode 100644
index 00000000000..355336ad7e2
--- /dev/null
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe 'getting an issue list for a project' do
+ include GraphqlHelpers
+
+ let(:project) { create(:project, :repository, :public) }
+ let(:current_user) { create(:user) }
+ let(:issues_data) { graphql_data['project']['issues']['edges'] }
+ let!(:issues) do
+ create(:issue, project: project, discussion_locked: true)
+ create(:issue, project: project)
+ end
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ #{all_graphql_fields_for('issues'.classify)}
+ }
+ }
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('issues', {}, fields)
+ )
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
+ it 'includes a web_url' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_data[0]['node']['webUrl']).to be_present
+ end
+
+ it 'includes discussion locked' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_data[0]['node']['discussionLocked']).to eq false
+ expect(issues_data[1]['node']['discussionLocked']).to eq true
+ end
+
+ context 'when the user does not have access to the issue' do
+ it 'returns nil' do
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+
+ post_graphql(query)
+
+ expect(issues_data).to eq []
+ end
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 3802b5c6848..688d91113ad 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -490,7 +490,7 @@ describe API::Groups do
expect(json_response.first['visibility']).not_to be_present
end
- it 'filters the groups projects' do
+ it "filters the groups projects" do
public_project = create(:project, :public, path: 'test1', group: group1)
get api("/groups/#{group1.id}/projects", user1), visibility: 'public'
@@ -502,6 +502,32 @@ describe API::Groups do
expect(json_response.first['name']).to eq(public_project.name)
end
+ it "returns projects excluding shared" do
+ create(:project_group_link, project: create(:project), group: group1)
+ create(:project_group_link, project: create(:project), group: group1)
+ create(:project_group_link, project: create(:project), group: group1)
+
+ get api("/groups/#{group1.id}/projects", user1), with_shared: false
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(2)
+ end
+
+ it "returns projects including those in subgroups", :nested_groups do
+ subgroup = create(:group, parent: group1)
+ create(:project, group: subgroup)
+ create(:project, group: subgroup)
+
+ get api("/groups/#{group1.id}/projects", user1), include_subgroups: true
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(4)
+ end
+
it "does not return a non existing group" do
get api("/groups/1328/projects", user1)
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index cca449e9e56..2c40e266f5f 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -206,6 +206,19 @@ describe API::Helpers do
expect { current_user }.to raise_error Gitlab::Auth::ExpiredError
end
+
+ context 'when impersonation is disabled' do
+ let(:personal_access_token) { create(:personal_access_token, :impersonation, user: user) }
+
+ before do
+ stub_config_setting(impersonation_enabled: false)
+ env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
+ end
+
+ it 'does not allow impersonation tokens' do
+ expect { current_user }.to raise_error Gitlab::Auth::ImpersonationDisabled
+ end
+ end
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 3d532dd83c7..1827da61e2d 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -300,17 +300,31 @@ describe API::Issues do
expect(json_response.first['state']).to eq('opened')
end
- it 'returns unlabeled issues for "No Label" label' do
- get api("/issues", user), labels: 'No Label'
+ it 'returns an empty array if no issue matches labels and state filters' do
+ get api("/issues", user), labels: label.title, state: :closed
+
+ expect_paginated_array_response(size: 0)
+ end
+
+ it 'returns an array of issues with any label' do
+ get api("/issues", user), labels: IssuesFinder::FILTER_ANY
expect_paginated_array_response(size: 1)
- expect(json_response.first['labels']).to be_empty
+ expect(json_response.first['id']).to eq(issue.id)
end
- it 'returns an empty array if no issue matches labels and state filters' do
- get api("/issues?labels=#{label.title}&state=closed", user)
+ it 'returns an array of issues with no label' do
+ get api("/issues", user), labels: IssuesFinder::FILTER_NONE
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(closed_issue.id)
+ end
+
+ it 'returns an array of issues with no label when using the legacy No+Label filter' do
+ get api("/issues", user), labels: "No Label"
+
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an empty array if no issue matches milestone' do
@@ -492,58 +506,58 @@ describe API::Issues do
end
it 'returns group issues without confidential issues for non project members' do
- get api("#{base_url}?state=opened", non_member)
+ get api(base_url, non_member), state: :opened
expect_paginated_array_response(size: 1)
expect(json_response.first['title']).to eq(group_issue.title)
end
it 'returns group confidential issues for author' do
- get api("#{base_url}?state=opened", author)
+ get api(base_url, author), state: :opened
expect_paginated_array_response(size: 2)
end
it 'returns group confidential issues for assignee' do
- get api("#{base_url}?state=opened", assignee)
+ get api(base_url, assignee), state: :opened
expect_paginated_array_response(size: 2)
end
it 'returns group issues with confidential issues for project members' do
- get api("#{base_url}?state=opened", user)
+ get api(base_url, user), state: :opened
expect_paginated_array_response(size: 2)
end
it 'returns group confidential issues for admin' do
- get api("#{base_url}?state=opened", admin)
+ get api(base_url, admin), state: :opened
expect_paginated_array_response(size: 2)
end
it 'returns an array of labeled group issues' do
- get api("#{base_url}?labels=#{group_label.title}", user)
+ get api(base_url, user), labels: group_label.title
expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([group_label.title])
end
it 'returns an array of labeled group issues where all labels match' do
- get api("#{base_url}?labels=#{group_label.title},foo,bar", user)
+ get api(base_url, user), labels: "#{group_label.title},foo,bar"
expect_paginated_array_response(size: 0)
end
it 'returns issues matching given search string for title' do
- get api("#{base_url}?search=#{group_issue.title}", user)
+ get api(base_url, user), search: group_issue.title
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns issues matching given search string for description' do
- get api("#{base_url}?search=#{group_issue.description}", user)
+ get api(base_url, user), search: group_issue.description
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
@@ -556,7 +570,7 @@ describe API::Issues do
create(:label_link, label: label_b, target: group_issue)
create(:label_link, label: label_c, target: group_issue)
- get api("#{base_url}", user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}"
+ get api(base_url, user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}"
expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
@@ -576,40 +590,55 @@ describe API::Issues do
end
it 'returns an empty array if no group issue matches labels' do
- get api("#{base_url}?labels=foo,bar", user)
+ get api(base_url, user), labels: 'foo,bar'
expect_paginated_array_response(size: 0)
end
+ it 'returns an array of group issues with any label' do
+ get api(base_url, user), labels: IssuesFinder::FILTER_ANY
+
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(group_issue.id)
+ end
+
+ it 'returns an array of group issues with no label' do
+ get api(base_url, user), labels: IssuesFinder::FILTER_NONE
+
+ response_ids = json_response.map { |issue| issue['id'] }
+
+ expect_paginated_array_response(size: 2)
+ expect(response_ids).to contain_exactly(group_closed_issue.id, group_confidential_issue.id)
+ end
+
it 'returns an empty array if no issue matches milestone' do
- get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)
+ get api(base_url, user), milestone: group_empty_milestone.title
expect_paginated_array_response(size: 0)
end
it 'returns an empty array if milestone does not exist' do
- get api("#{base_url}?milestone=foo", user)
+ get api(base_url, user), milestone: 'foo'
expect_paginated_array_response(size: 0)
end
it 'returns an array of issues in given milestone' do
- get api("#{base_url}?state=opened&milestone=#{group_milestone.title}", user)
+ get api(base_url, user), state: :opened, milestone: group_milestone.title
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an array of issues matching state in milestone' do
- get api("#{base_url}?milestone=#{group_milestone.title}"\
- '&state=closed', user)
+ get api(base_url, user), milestone: group_milestone.title, state: :closed
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_closed_issue.id)
end
it 'returns an array of issues with no milestone' do
- get api("#{base_url}?milestone=#{no_milestone_title}", user)
+ get api(base_url, user), milestone: no_milestone_title
expect(response).to have_gitlab_http_status(200)
@@ -645,7 +674,7 @@ describe API::Issues do
end
it 'sorts by updated_at ascending when requested' do
- get api("#{base_url}?order_by=updated_at&sort=asc", user)
+ get api(base_url, user), order_by: :updated_at, sort: :asc
response_dates = json_response.map { |issue| issue['updated_at'] }
@@ -748,7 +777,7 @@ describe API::Issues do
end
it 'returns an array of labeled project issues' do
- get api("#{base_url}/issues?labels=#{label.title}", user)
+ get api("#{base_url}/issues", user), labels: label.title
expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label.title])
@@ -800,26 +829,42 @@ describe API::Issues do
expect_paginated_array_response(size: 0)
end
+ it 'returns an array of project issues with any label' do
+ get api("#{base_url}/issues", user), labels: IssuesFinder::FILTER_ANY
+
+ expect_paginated_array_response(size: 1)
+ expect(json_response.first['id']).to eq(issue.id)
+ end
+
+ it 'returns an array of project issues with no label' do
+ get api("#{base_url}/issues", user), labels: IssuesFinder::FILTER_NONE
+
+ response_ids = json_response.map { |issue| issue['id'] }
+
+ expect_paginated_array_response(size: 2)
+ expect(response_ids).to contain_exactly(closed_issue.id, confidential_issue.id)
+ end
+
it 'returns an empty array if no project issue matches labels' do
- get api("#{base_url}/issues?labels=foo,bar", user)
+ get api("#{base_url}/issues", user), labels: 'foo,bar'
expect_paginated_array_response(size: 0)
end
it 'returns an empty array if no issue matches milestone' do
- get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user)
+ get api("#{base_url}/issues", user), milestone: empty_milestone.title
expect_paginated_array_response(size: 0)
end
it 'returns an empty array if milestone does not exist' do
- get api("#{base_url}/issues?milestone=foo", user)
+ get api("#{base_url}/issues", user), milestone: :foo
expect_paginated_array_response(size: 0)
end
it 'returns an array of issues in given milestone' do
- get api("#{base_url}/issues?milestone=#{milestone.title}", user)
+ get api("#{base_url}/issues", user), milestone: milestone.title
expect_paginated_array_response(size: 2)
expect(json_response.first['id']).to eq(issue.id)
@@ -827,21 +872,21 @@ describe API::Issues do
end
it 'returns an array of issues matching state in milestone' do
- get api("#{base_url}/issues?milestone=#{milestone.title}&state=closed", user)
+ get api("#{base_url}/issues", user), milestone: milestone.title, state: :closed
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an array of issues with no milestone' do
- get api("#{base_url}/issues?milestone=#{no_milestone_title}", user)
+ get api("#{base_url}/issues", user), milestone: no_milestone_title
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(confidential_issue.id)
end
it 'returns an array of issues with any milestone' do
- get api("#{base_url}/issues?milestone=#{any_milestone_title}", user)
+ get api("#{base_url}/issues", user), milestone: any_milestone_title
response_ids = json_response.map { |issue| issue['id'] }
@@ -859,7 +904,7 @@ describe API::Issues do
end
it 'sorts ascending when requested' do
- get api("#{base_url}/issues?sort=asc", user)
+ get api("#{base_url}/issues", user), sort: :asc
response_dates = json_response.map { |issue| issue['created_at'] }
@@ -868,7 +913,7 @@ describe API::Issues do
end
it 'sorts by updated_at descending when requested' do
- get api("#{base_url}/issues?order_by=updated_at", user)
+ get api("#{base_url}/issues", user), order_by: :updated_at
response_dates = json_response.map { |issue| issue['updated_at'] }
@@ -877,7 +922,7 @@ describe API::Issues do
end
it 'sorts by updated_at ascending when requested' do
- get api("#{base_url}/issues?order_by=updated_at&sort=asc", user)
+ get api("#{base_url}/issues", user), order_by: :updated_at, sort: :asc
response_dates = json_response.map { |issue| issue['updated_at'] }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e4e0ca285e0..27bcde77860 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -359,6 +359,8 @@ describe API::MergeRequests do
expect(json_response['should_close_merge_request']).to be_falsy
expect(json_response['force_close_merge_request']).to be_falsy
expect(json_response['changes_count']).to eq(merge_request.merge_request_diff.real_size)
+ expect(json_response['merge_error']).to eq(merge_request.merge_error)
+ expect(json_response).not_to include('rebase_in_progress')
end
it 'exposes description and title html when render_html is true' do
@@ -369,6 +371,14 @@ describe API::MergeRequests do
expect(json_response).to include('title_html', 'description_html')
end
+ it 'exposes rebase_in_progress when include_rebase_in_progress is true' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), include_rebase_in_progress: true
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response).to include('rebase_in_progress')
+ end
+
context 'merge_request_metrics' do
before do
merge_request.metrics.update!(merged_by: user,
@@ -1181,6 +1191,26 @@ describe API::MergeRequests do
end
end
+ describe 'PUT :id/merge_requests/:merge_request_iid/rebase' do
+ it 'enqueues a rebase of the merge request against the target branch' do
+ Sidekiq::Testing.fake! do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/rebase", user)
+ end
+
+ expect(response).to have_gitlab_http_status(202)
+ expect(RebaseWorker.jobs.size).to eq(1)
+ end
+
+ it 'returns 403 if the user cannot push to the branch' do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/rebase", guest)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
describe 'Time tracking' do
let(:issuable) { merge_request }
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index c8fa4754810..204702b8a5a 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -42,7 +42,7 @@ describe API::ProjectImport do
end
it 'does not schedule an import for a namespace that does not exist' do
- expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
expect(::Projects::CreateService).not_to receive(:new)
post api('/projects/import', user), namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file)
@@ -52,7 +52,7 @@ describe API::ProjectImport do
end
it 'does not schedule an import if the user has no permission to the namespace' do
- expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
post(api('/projects/import', create(:user)),
path: 'test-import3',
@@ -64,7 +64,7 @@ describe API::ProjectImport do
end
it 'does not schedule an import if the user uploads no valid file' do
- expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
post api('/projects/import', user), path: 'test-import3', file: './random/test'
@@ -119,7 +119,7 @@ describe API::ProjectImport do
let(:existing_project) { create(:project, namespace: user.namespace) }
it 'does not schedule an import' do
- expect_any_instance_of(Project).not_to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file)
@@ -139,7 +139,7 @@ describe API::ProjectImport do
end
def stub_import(namespace)
- expect_any_instance_of(Project).to receive(:import_schedule)
+ expect_any_instance_of(ProjectImportState).to receive(:schedule)
expect(::Projects::CreateService).to receive(:new).with(user, hash_including(namespace_id: namespace.id)).and_call_original
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index fa38751fe58..de141377793 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -168,6 +168,12 @@ describe API::Repositories do
expect(response).to have_gitlab_http_status(200)
end
+ it 'forces attachment content disposition' do
+ get api(route, current_user)
+
+ expect(headers['Content-Disposition']).to match(/^attachment/)
+ end
+
context 'when sha does not exist' do
it_behaves_like '404 response' do
let(:request) { get api(route.sub(sample_blob.oid, '123456'), current_user) }
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 909703a8d47..b36087b86a7 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -830,6 +830,18 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(job.trace.raw).to eq 'BUILD TRACE UPDATED'
expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED'
end
+
+ context 'when concurrent update of trace is happening' do
+ before do
+ job.trace.write('wb') do
+ update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
+ end
+ end
+
+ it 'returns that operation conflicts' do
+ expect(response.status).to eq(409)
+ end
+ end
end
context 'when no trace is given' do
@@ -1022,6 +1034,18 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'when concurrent update of trace is happening' do
+ before do
+ job.trace.write('wb') do
+ patch_the_trace
+ end
+ end
+
+ it 'returns that operation conflicts' do
+ expect(response.status).to eq(409)
+ end
+ end
+
context 'when the job is canceled' do
before do
job.cancel
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index 6da769cb3ed..c546ba3e127 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -94,6 +94,12 @@ describe API::Snippets do
expect(response.body).to eq(snippet.content)
end
+ it 'forces attachment content disposition' do
+ get api("/snippets/#{snippet.id}/raw", user)
+
+ expect(headers['Content-Disposition']).to match(/^attachment/)
+ end
+
it 'returns 404 for invalid snippet id' do
get api("/snippets/1234/raw", user)
diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb
index 8f795bb561e..c348fc0efac 100644
--- a/spec/rubocop/cop/migration/add_reference_spec.rb
+++ b/spec/rubocop/cop/migration/add_reference_spec.rb
@@ -29,7 +29,7 @@ describe RuboCop::Cop::Migration::AddReference do
expect_offense(<<~RUBY)
call do
add_reference(:projects, :users)
- ^^^^^^^^^^^^^ `add_reference` requires `index: true`
+ ^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
end
RUBY
end
@@ -38,7 +38,7 @@ describe RuboCop::Cop::Migration::AddReference do
expect_offense(<<~RUBY)
def up
add_reference(:projects, :users, index: false)
- ^^^^^^^^^^^^^ `add_reference` requires `index: true`
+ ^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
end
RUBY
end
@@ -50,5 +50,13 @@ describe RuboCop::Cop::Migration::AddReference do
end
RUBY
end
+
+ it 'does not register an offense when the index is unique' do
+ expect_no_offenses(<<~RUBY)
+ def up
+ add_reference(:projects, :users, index: { unique: true } )
+ end
+ RUBY
+ end
end
end
diff --git a/spec/rubocop/cop/safe_params_spec.rb b/spec/rubocop/cop/safe_params_spec.rb
new file mode 100644
index 00000000000..4f02b8e9008
--- /dev/null
+++ b/spec/rubocop/cop/safe_params_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/safe_params'
+
+describe RuboCop::Cop::SafeParams do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'flags the params as an argument of url_for' do
+ expect_offense(<<~SOURCE)
+ url_for(params)
+ ^^^^^^^^^^^^^^^ Use `safe_params` instead of `params` in url_for.
+ SOURCE
+ end
+
+ it 'flags the merged params as an argument of url_for' do
+ expect_offense(<<~SOURCE)
+ url_for(params.merge(additional_params))
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `safe_params` instead of `params` in url_for.
+ SOURCE
+ end
+
+ it 'flags the merged params arg as an argument of url_for' do
+ expect_offense(<<~SOURCE)
+ url_for(something.merge(additional).merge(params))
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `safe_params` instead of `params` in url_for.
+ SOURCE
+ end
+
+ it 'does not flag other argument of url_for' do
+ expect_no_offenses(<<~SOURCE)
+ url_for(something)
+ SOURCE
+ end
+end
diff --git a/spec/services/ci/archive_trace_service_spec.rb b/spec/services/ci/archive_trace_service_spec.rb
new file mode 100644
index 00000000000..8e9cb65f3bc
--- /dev/null
+++ b/spec/services/ci/archive_trace_service_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Ci::ArchiveTraceService, '#execute' do
+ subject { described_class.new.execute(job) }
+
+ context 'when job is finished' do
+ let(:job) { create(:ci_build, :success, :trace_live) }
+
+ it 'creates an archived trace' do
+ expect { subject }.not_to raise_error
+
+ expect(job.reload.job_artifacts_trace).to be_exist
+ end
+
+ context 'when trace is already archived' do
+ let!(:job) { create(:ci_build, :success, :trace_artifact) }
+
+ it 'ignores an exception' do
+ expect { subject }.not_to raise_error
+ end
+
+ it 'does not create an archived trace' do
+ expect { subject }.not_to change { Ci::JobArtifact.trace.count }
+ end
+ end
+ end
+
+ context 'when job is running' do
+ let(:job) { create(:ci_build, :running, :trace_live) }
+
+ it 'increments Prometheus counter, sends crash report to Sentry and ignore an error for continuing to archive' do
+ expect(Gitlab::Sentry)
+ .to receive(:track_exception)
+ .with(::Gitlab::Ci::Trace::ArchiveError,
+ issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
+ extra: { job_id: job.id } ).once
+
+ expect(Rails.logger)
+ .to receive(:error)
+ .with("Failed to archive trace. id: #{job.id} message: Job is not finished yet")
+ .and_call_original
+
+ expect(Gitlab::Metrics)
+ .to receive(:counter)
+ .with(:job_trace_archive_failed_total, "Counter of failed attempts of trace archiving")
+ .and_call_original
+
+ expect { subject }.not_to raise_error
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
index eb0bdb61ee3..f3036fbcb0e 100644
--- a/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
+++ b/spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb
@@ -28,41 +28,7 @@ describe Clusters::Applications::CheckIngressIpAddressService do
allow(application.cluster).to receive(:kubeclient).and_return(kubeclient)
end
- describe '#execute' do
- context 'when the ingress ip address is available' do
- it 'updates the external_ip for the app' do
- subject
+ include_examples 'check ingress ip executions', :clusters_applications_ingress
- expect(application.external_ip).to eq('111.222.111.222')
- end
- end
-
- context 'when the ingress ip address is not available' do
- let(:ingress) { nil }
-
- it 'does not error' do
- subject
- end
- end
-
- context 'when the exclusive lease cannot be obtained' do
- it 'does not call kubeclient' do
- stub_exclusive_lease_taken(lease_key, timeout: 15.seconds.to_i)
-
- subject
-
- expect(kubeclient).not_to have_received(:get_service)
- end
- end
-
- context 'when there is already an external_ip' do
- let(:application) { create(:clusters_applications_ingress, :installed, external_ip: '001.111.002.111') }
-
- it 'does not call kubeclient' do
- subject
-
- expect(kubeclient).not_to have_received(:get_service)
- end
- end
- end
+ include_examples 'check ingress ip executions', :clusters_applications_knative
end
diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
index ea17f2bb423..45b8ce94815 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -8,14 +8,6 @@ describe Clusters::Applications::CheckInstallationProgressService do
let(:phase) { Gitlab::Kubernetes::Pod::UNKNOWN }
let(:errors) { nil }
- shared_examples 'a terminated installation' do
- it 'removes the installation POD' do
- expect(service).to receive(:remove_installation_pod).once
-
- service.execute
- end
- end
-
shared_examples 'a not yet terminated installation' do |a_phase|
let(:phase) { a_phase }
@@ -39,15 +31,13 @@ describe Clusters::Applications::CheckInstallationProgressService do
context 'when timeouted' do
let(:application) { create(:clusters_applications_helm, :timeouted) }
- it_behaves_like 'a terminated installation'
-
it 'make the application errored' do
expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
service.execute
expect(application).to be_errored
- expect(application.status_reason).to match(/\btimed out\b/)
+ expect(application.status_reason).to eq("Installation timed out. Check pod logs for install-helm for more details.")
end
end
end
@@ -66,7 +56,11 @@ describe Clusters::Applications::CheckInstallationProgressService do
expect(service).to receive(:installation_phase).once.and_return(phase)
end
- it_behaves_like 'a terminated installation'
+ it 'removes the installation POD' do
+ expect(service).to receive(:remove_installation_pod).once
+
+ service.execute
+ end
it 'make the application installed' do
expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
@@ -86,13 +80,11 @@ describe Clusters::Applications::CheckInstallationProgressService do
expect(service).to receive(:installation_phase).once.and_return(phase)
end
- it_behaves_like 'a terminated installation'
-
it 'make the application errored' do
service.execute
expect(application).to be_errored
- expect(application.status_reason).to eq("Installation failed")
+ expect(application.status_reason).to eq("Installation failed. Check pod logs for install-helm for more details.")
end
end
@@ -113,6 +105,12 @@ describe Clusters::Applications::CheckInstallationProgressService do
expect(application).to be_errored
expect(application.status_reason).to eq('Kubernetes error: 401')
end
+
+ it 'should log error' do
+ expect(service.send(:logger)).to receive(:error)
+
+ service.execute
+ end
end
end
end
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index 2f801d019fe..018d9822d3e 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -33,8 +33,9 @@ describe Clusters::Applications::InstallService do
end
context 'when k8s cluster communication fails' do
+ let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) }
+
before do
- error = Kubeclient::HttpError.new(500, 'system failure', nil)
expect(helm_client).to receive(:install).with(install_command).and_raise(error)
end
@@ -44,18 +45,81 @@ describe Clusters::Applications::InstallService do
expect(application).to be_errored
expect(application.status_reason).to match('Kubernetes error: 500')
end
+
+ it 'logs errors' do
+ expect(service.send(:logger)).to receive(:error).with(
+ {
+ exception: 'Kubeclient::HttpError',
+ message: 'system failure',
+ service: 'Clusters::Applications::InstallService',
+ app_id: application.id,
+ project_ids: application.cluster.project_ids,
+ group_ids: [],
+ error_code: 500
+ }
+ )
+
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
+ error,
+ extra: {
+ exception: 'Kubeclient::HttpError',
+ message: 'system failure',
+ service: 'Clusters::Applications::InstallService',
+ app_id: application.id,
+ project_ids: application.cluster.project_ids,
+ group_ids: [],
+ error_code: 500
+ }
+ )
+
+ service.execute
+ end
end
- context 'when application cannot be persisted' do
+ context 'a non kubernetes error happens' do
let(:application) { create(:clusters_applications_helm, :scheduled) }
+ let(:error) { StandardError.new("something bad happened") }
+
+ before do
+ expect(application).to receive(:make_installing!).once.and_raise(error)
+ end
it 'make the application errored' do
- expect(application).to receive(:make_installing!).once.and_raise(ActiveRecord::RecordInvalid)
expect(helm_client).not_to receive(:install)
service.execute
expect(application).to be_errored
+ expect(application.status_reason).to eq("Can't start installation process.")
+ end
+
+ it 'logs errors' do
+ expect(service.send(:logger)).to receive(:error).with(
+ {
+ exception: 'StandardError',
+ error_code: nil,
+ message: 'something bad happened',
+ service: 'Clusters::Applications::InstallService',
+ app_id: application.id,
+ project_ids: application.cluster.projects.pluck(:id),
+ group_ids: []
+ }
+ )
+
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
+ error,
+ extra: {
+ exception: 'StandardError',
+ error_code: nil,
+ message: 'something bad happened',
+ service: 'Clusters::Applications::InstallService',
+ app_id: application.id,
+ project_ids: application.cluster.projects.pluck(:id),
+ group_ids: []
+ }
+ )
+
+ service.execute
end
end
end
diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb
index 5f3c8e82715..84c48d63c64 100644
--- a/spec/services/files/multi_service_spec.rb
+++ b/spec/services/files/multi_service_spec.rb
@@ -122,26 +122,47 @@ describe Files::MultiService do
let(:action) { 'move' }
let(:new_file_path) { 'files/ruby/new_popen.rb' }
+ let(:result) { subject.execute }
+ let(:blob) { repository.blob_at_branch(branch_name, new_file_path) }
+
context 'when original file has been updated' do
before do
update_file(original_file_path)
end
it 'rejects the commit' do
- results = subject.execute
-
- expect(results[:status]).to eq(:error)
- expect(results[:message]).to match(original_file_path)
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to match(original_file_path)
end
end
- context 'when original file have not been updated' do
+ context 'when original file has not been updated' do
it 'moves the file' do
- results = subject.execute
- blob = project.repository.blob_at_branch(branch_name, new_file_path)
-
- expect(results[:status]).to eq(:success)
+ expect(result[:status]).to eq(:success)
expect(blob).to be_present
+ expect(blob.data).to eq(file_content)
+ end
+
+ context 'when content is nil' do
+ let(:file_content) { nil }
+
+ it 'moves the existing content untouched' do
+ original_content = repository.blob_at_branch(branch_name, original_file_path).data
+
+ expect(result[:status]).to eq(:success)
+ expect(blob).to be_present
+ expect(blob.data).to eq(original_content)
+ end
+ end
+
+ context 'when content is an empty string' do
+ let(:file_content) { '' }
+
+ it 'moves the file and empties it' do
+ expect(result[:status]).to eq(:success)
+ expect(blob).not_to be_nil
+ expect(blob.data).to eq('')
+ end
end
end
end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index c9a668994eb..1894d8c8d0e 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -21,15 +21,20 @@ describe MergeRequests::BuildService do
let(:commit_2) { double(:commit_2, sha: 'f00ba7', safe_message: 'This is a bad commit message!') }
let(:commits) { nil }
+ let(:params) do
+ {
+ description: description,
+ source_branch: source_branch,
+ target_branch: target_branch,
+ source_project: source_project,
+ target_project: target_project,
+ milestone_id: milestone_id,
+ label_ids: label_ids
+ }
+ end
+
let(:service) do
- described_class.new(project, user,
- description: description,
- source_branch: source_branch,
- target_branch: target_branch,
- source_project: source_project,
- target_project: target_project,
- milestone_id: milestone_id,
- label_ids: label_ids)
+ described_class.new(project, user, params)
end
before do
@@ -56,6 +61,19 @@ describe MergeRequests::BuildService do
merge_request
end
+ it 'does not assign force_remove_source_branch' do
+ expect(merge_request.force_remove_source_branch?).to be_falsey
+ end
+
+ context 'with force_remove_source_branch parameter' do
+ let(:mr_params) { params.merge(force_remove_source_branch: '1') }
+ let(:merge_request) { described_class.new(project, user, mr_params).execute }
+
+ it 'assigns force_remove_source_branch' do
+ expect(merge_request.force_remove_source_branch?).to be_truthy
+ end
+ end
+
context 'missing source branch' do
let(:source_branch) { '' }
diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
index 8838742a637..52bbd4e794d 100644
--- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
@@ -95,7 +95,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
sha: '1234abcdef', status: 'success')
end
- it 'it does not merge merge request' do
+ it 'it does not merge request' do
expect(MergeWorker).not_to receive(:perform_async)
service.trigger(old_pipeline)
end
diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb
index 141ccf7c4d8..da078dd36c6 100644
--- a/spec/services/projects/create_from_template_service_spec.rb
+++ b/spec/services/projects/create_from_template_service_spec.rb
@@ -47,7 +47,7 @@ describe Projects::CreateFromTemplateService do
end
it 'is not scheduled' do
- expect(project.import_scheduled?).to be(false)
+ expect(project.import_scheduled?).to be_nil
end
it 'repository is empty' do
diff --git a/spec/services/users/set_status_service_spec.rb b/spec/services/users/set_status_service_spec.rb
index 8a8458ab9de..7c26be48345 100644
--- a/spec/services/users/set_status_service_spec.rb
+++ b/spec/services/users/set_status_service_spec.rb
@@ -7,7 +7,7 @@ describe Users::SetStatusService do
subject(:service) { described_class.new(current_user, params) }
describe '#execute' do
- context 'when when params are set' do
+ context 'when params are set' do
let(:params) { { emoji: 'taurus', message: 'a random status' } }
it 'creates a status' do
diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
new file mode 100644
index 00000000000..7e4958f177a
--- /dev/null
+++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
@@ -0,0 +1,92 @@
+shared_examples 'authenticates sessionless user' do |path, format, params|
+ params ||= {}
+
+ before do
+ stub_authentication_activity_metrics(debug: false)
+ end
+
+ let(:user) { create(:user) }
+ let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let(:default_params) { { format: format }.merge(params.except(:public) || {}) }
+
+ context "when the 'personal_access_token' param is populated with the personal access token" do
+ it 'logs the user in' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
+
+ get path, default_params.merge(private_token: personal_access_token.token)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(controller.current_user).to eq(user)
+ end
+
+ it 'does not log the user in if page is public', if: params[:public] do
+ get path, default_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(controller.current_user).to be_nil
+ end
+ end
+
+ context 'when the personal access token has no api scope', unless: params[:public] do
+ it 'does not log the user in' do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+
+ personal_access_token.update(scopes: [:read_user])
+
+ get path, default_params.merge(private_token: personal_access_token.token)
+
+ expect(response).not_to have_gitlab_http_status(200)
+ end
+ end
+
+ context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
+ it 'logs the user in' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
+
+ @request.headers['PRIVATE-TOKEN'] = personal_access_token.token
+ get path, default_params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context "when the 'feed_token' param is populated with the feed token", if: format == :rss do
+ it "logs the user in" do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
+
+ get path, default_params.merge(feed_token: user.feed_token)
+
+ expect(response).to have_gitlab_http_status 200
+ end
+ end
+
+ context "when the 'feed_token' param is populated with an invalid feed token", if: format == :rss, unless: params[:public] do
+ it "logs the user" do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+
+ get path, default_params.merge(feed_token: 'token')
+
+ expect(response.status).not_to eq 200
+ end
+ end
+
+ it "doesn't log the user in otherwise", unless: params[:public] do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+
+ get path, default_params.merge(private_token: 'token')
+
+ expect(response.status).not_to eq(200)
+ end
+end
diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb
deleted file mode 100644
index 614aaa73693..00000000000
--- a/spec/support/gitaly.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-RSpec.configure do |config|
- config.before(:each) do |example|
- if example.metadata[:disable_gitaly]
- # Use 'and_wrap_original' to make sure the arguments are valid
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_wrap_original { |m, *args| m.call(*args) && false }
- else
- next if example.metadata[:skip_gitaly_mock]
-
- # Use 'and_wrap_original' to make sure the arguments are valid
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_wrap_original do |m, *args|
- m.call(*args)
- !Gitlab::GitalyClient.explicit_opt_in_required.include?(args.first)
- end
- end
- end
-end
diff --git a/spec/support/helpers/gpg_helpers.rb b/spec/support/helpers/gpg_helpers.rb
index 3f7279a50e0..8d1637228d0 100644
--- a/spec/support/helpers/gpg_helpers.rb
+++ b/spec/support/helpers/gpg_helpers.rb
@@ -1,5 +1,9 @@
+# frozen_string_literal: true
+
module GpgHelpers
- SIGNED_COMMIT_SHA = '8a852d50dda17cc8fd1408d2fd0c5b0f24c76ca4'.freeze
+ SIGNED_COMMIT_SHA = '8a852d50dda17cc8fd1408d2fd0c5b0f24c76ca4'
+ SIGNED_AND_AUTHORED_SHA = '3c1d9a0266cb0c62d926f4a6c649beed561846f5'
+ DIFFERING_EMAIL_SHA = 'a17a9f66543673edf0a3d1c6b93bdda3fe600f32'
module User1
extend self
diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb
index 4212be2cc88..ce1f9fce10d 100644
--- a/spec/support/helpers/prometheus_helpers.rb
+++ b/spec/support/helpers/prometheus_helpers.rb
@@ -49,11 +49,11 @@ module PrometheusHelpers
"https://prometheus.example.com/api/v1/series?#{query}"
end
- def stub_prometheus_request(url, body: {}, status: 200)
+ def stub_prometheus_request(url, body: {}, status: 200, headers: {})
WebMock.stub_request(:get, url)
.to_return({
status: status,
- headers: { 'Content-Type' => 'application/json' },
+ headers: { 'Content-Type' => 'application/json' }.merge(headers),
body: body.to_json
})
end
diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb
index d9ed405baf4..a49036c3b80 100644
--- a/spec/support/import_export/export_file_helper.rb
+++ b/spec/support/import_export/export_file_helper.rb
@@ -123,7 +123,7 @@ module ExportFileHelper
false
end
- # Compares model attributes with those those found in the hash
+ # Compares model attributes with those found in the hash
# and returns true if there is a match, ignoring some excluded attributes.
def safe_model?(model, excluded_attributes, parent)
excluded_attributes += associations_for(model)
diff --git a/spec/support/shared_contexts/url_shared_context.rb b/spec/support/shared_contexts/url_shared_context.rb
new file mode 100644
index 00000000000..1b1f67daac3
--- /dev/null
+++ b/spec/support/shared_contexts/url_shared_context.rb
@@ -0,0 +1,17 @@
+shared_context 'invalid urls' do
+ let(:urls_with_CRLF) do
+ ["http://127.0.0.1:333/pa\rth",
+ "http://127.0.0.1:333/pa\nth",
+ "http://127.0a.0.1:333/pa\r\nth",
+ "http://127.0.0.1:333/path?param=foo\r\nbar",
+ "http://127.0.0.1:333/path?param=foo\rbar",
+ "http://127.0.0.1:333/path?param=foo\nbar",
+ "http://127.0.0.1:333/pa%0dth",
+ "http://127.0.0.1:333/pa%0ath",
+ "http://127.0a.0.1:333/pa%0d%0th",
+ "http://127.0.0.1:333/pa%0D%0Ath",
+ "http://127.0.0.1:333/path?param=foo%0Abar",
+ "http://127.0.0.1:333/path?param=foo%0Dbar",
+ "http://127.0.0.1:333/path?param=foo%0D%0Abar"]
+ end
+end
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index 94e82b8ce90..377bd82b67e 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -272,16 +272,11 @@ shared_examples_for 'common trace features' do
include ExclusiveLeaseHelpers
before do
- stub_exclusive_lease_taken("trace:archive:#{trace.job.id}", timeout: 1.hour)
+ stub_exclusive_lease_taken("trace:write:lock:#{trace.job.id}", timeout: 1.minute)
end
it 'blocks concurrent archiving' do
- expect(Rails.logger).to receive(:error).with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
-
- subject
-
- build.reload
- expect(build.job_artifacts_trace).to be_nil
+ expect { subject }.to raise_error(::Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
end
end
end
diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb
index 668a390b5d2..92d4dd598d5 100644
--- a/spec/support/shared_examples/requests/api/merge_requests_list.rb
+++ b/spec/support/shared_examples/requests/api/merge_requests_list.rb
@@ -186,6 +186,23 @@ shared_examples 'merge requests list' do
expect(json_response.length).to eq(0)
end
+ it 'returns an array of merge requests with any label when filtering by any label' do
+ get api(endpoint_path, user), labels: IssuesFinder::FILTER_ANY
+
+ expect_paginated_array_response
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request.id)
+ end
+
+ it 'returns an array of merge requests without a label when filtering by no label' do
+ get api(endpoint_path, user), labels: IssuesFinder::FILTER_NONE
+
+ response_ids = json_response.map { |merge_request| merge_request['id'] }
+
+ expect_paginated_array_response
+ expect(response_ids).to contain_exactly(merge_request_closed.id, merge_request_merged.id, merge_request_locked.id)
+ end
+
it 'returns an array of labeled merge requests that are merged for a milestone' do
bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project)
diff --git a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
new file mode 100644
index 00000000000..14638a574a5
--- /dev/null
+++ b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
@@ -0,0 +1,33 @@
+shared_examples 'check ingress ip executions' do |app_name|
+ describe '#execute' do
+ let(:application) { create(app_name, :installed) }
+ let(:service) { described_class.new(application) }
+ let(:kubeclient) { double(::Kubeclient::Client, get_service: kube_service) }
+
+ context 'when the ingress ip address is available' do
+ it 'updates the external_ip for the app' do
+ subject
+
+ expect(application.external_ip).to eq('111.222.111.222')
+ end
+ end
+
+ context 'when the ingress ip address is not available' do
+ let(:ingress) { nil }
+
+ it 'does not error' do
+ subject
+ end
+ end
+
+ context 'when the exclusive lease cannot be obtained' do
+ it 'does not call kubeclient' do
+ stub_exclusive_lease_taken(lease_key, timeout: 15.seconds.to_i)
+
+ subject
+
+ expect(kubeclient).not_to have_received(:get_service)
+ end
+ end
+ end
+end
diff --git a/spec/tasks/cache/clear/redis_spec.rb b/spec/tasks/cache/clear/redis_spec.rb
index cca2b864e9b..97c8c943f3a 100644
--- a/spec/tasks/cache/clear/redis_spec.rb
+++ b/spec/tasks/cache/clear/redis_spec.rb
@@ -6,7 +6,10 @@ describe 'clearing redis cache' do
end
describe 'clearing pipeline status cache' do
- let(:pipeline_status) { create(:ci_pipeline).project.pipeline_status }
+ let(:pipeline_status) do
+ project = create(:project, :repository)
+ create(:ci_pipeline, project: project).project.pipeline_status
+ end
before do
allow(pipeline_status).to receive(:loaded).and_return(nil)
diff --git a/spec/tasks/gitlab/site_statistics_rake_spec.rb b/spec/tasks/gitlab/site_statistics_rake_spec.rb
deleted file mode 100644
index c43ce25a540..00000000000
--- a/spec/tasks/gitlab/site_statistics_rake_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-require 'rake_helper'
-
-describe 'rake gitlab:refresh_site_statistics' do
- before do
- Rake.application.rake_require 'tasks/gitlab/site_statistics'
-
- create(:project)
- SiteStatistic.fetch.update(repositories_count: 0)
- end
-
- let(:task) { 'gitlab:refresh_site_statistics' }
-
- it 'recalculates existing counters' do
- run_rake_task(task)
-
- expect(SiteStatistic.fetch.repositories_count).to eq(1)
- end
-
- it 'displays message listing counters' do
- expect { run_rake_task(task) }.to output(/Updating Site Statistics counters:.* Repositories\.\.\. OK!/m).to_stdout
- end
-end
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
index ab6100509a6..082d09d3f16 100644
--- a/spec/validators/url_validator_spec.rb
+++ b/spec/validators/url_validator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UrlValidator do
@@ -6,6 +8,30 @@ describe UrlValidator do
include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS
+ describe 'validations' do
+ include_context 'invalid urls'
+
+ let(:validator) { described_class.new(attributes: [:link_url]) }
+
+ it 'returns error when url is nil' do
+ expect(validator.validate_each(badge, :link_url, nil)).to be_nil
+ expect(badge.errors.first[1]).to eq 'must be a valid URL'
+ end
+
+ it 'returns error when url is empty' do
+ expect(validator.validate_each(badge, :link_url, '')).to be_nil
+ expect(badge.errors.first[1]).to eq 'must be a valid URL'
+ end
+
+ it 'does not allow urls with CR or LF characters' do
+ aggregate_failures do
+ urls_with_CRLF.each do |url|
+ expect(validator.validate_each(badge, :link_url, url)[0]).to eq 'is blocked: URI is invalid'
+ end
+ end
+ end
+ end
+
context 'by default' do
let(:validator) { described_class.new(attributes: [:link_url]) }
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
new file mode 100644
index 00000000000..2e19d0cec26
--- /dev/null
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'layouts/header/_new_dropdown' do
+ let(:user) { create(:user) }
+
+ context 'group-specific links' do
+ let(:group) { create(:group) }
+
+ before do
+ stub_current_user(user)
+
+ assign(:group, group)
+ end
+
+ context 'as a Group owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'has a "New project" link' do
+ render
+
+ expect(rendered).to have_link(
+ 'New project',
+ href: new_project_path(namespace_id: group.id)
+ )
+ end
+
+ it 'has a "New subgroup" link', :nested_groups do
+ render
+
+ expect(rendered).to have_link(
+ 'New subgroup',
+ href: new_group_path(parent_id: group.id)
+ )
+ end
+ end
+ end
+
+ context 'project-specific links' do
+ let(:project) { create(:project, creator: user, namespace: user.namespace) }
+
+ before do
+ assign(:project, project)
+ end
+
+ context 'as a Project owner' do
+ before do
+ stub_current_user(user)
+ end
+
+ it 'has a "New issue" link' do
+ render
+
+ expect(rendered).to have_link(
+ 'New issue',
+ href: new_project_issue_path(project)
+ )
+ end
+
+ it 'has a "New merge request" link' do
+ render
+
+ expect(rendered).to have_link(
+ 'New merge request',
+ href: project_new_merge_request_path(project)
+ )
+ end
+
+ it 'has a "New snippet" link' do
+ render
+
+ expect(rendered).to have_link(
+ 'New snippet',
+ href: new_project_snippet_path(project)
+ )
+ end
+ end
+
+ context 'as a Project guest' do
+ let(:guest) { create(:user) }
+
+ before do
+ stub_current_user(guest)
+ project.add_guest(guest)
+ end
+
+ it 'has no "New merge request" link' do
+ render
+
+ expect(rendered).not_to have_link('New merge request')
+ end
+
+ it 'has no "New snippet" link' do
+ render
+
+ expect(rendered).not_to have_link(
+ 'New snippet',
+ href: new_project_snippet_path(project)
+ )
+ end
+ end
+ end
+
+ context 'global links' do
+ before do
+ stub_current_user(user)
+ end
+
+ it 'has a "New project" link' do
+ render
+
+ expect(rendered).to have_link('New project', href: new_project_path)
+ end
+
+ it 'has a "New group" link' do
+ render
+
+ expect(rendered).to have_link('New group', href: new_group_path)
+ end
+
+ it 'has a "New snippet" link' do
+ render
+
+ expect(rendered).to have_link('New snippet', href: new_snippet_path)
+ end
+ end
+
+ def stub_current_user(current_user)
+ allow(view).to receive(:current_user).and_return(current_user)
+ end
+end
diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
index b768588c6e1..7244ad4f199 100644
--- a/spec/workers/archive_trace_worker_spec.rb
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -5,10 +5,11 @@ describe ArchiveTraceWorker do
subject { described_class.new.perform(job&.id) }
context 'when job is found' do
- let(:job) { create(:ci_build) }
+ let(:job) { create(:ci_build, :trace_live) }
it 'executes service' do
- expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!)
+ expect_any_instance_of(Ci::ArchiveTraceService)
+ .to receive(:execute).with(job)
subject
end
@@ -18,7 +19,8 @@ describe ArchiveTraceWorker do
let(:job) { nil }
it 'does not execute service' do
- expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!)
+ expect_any_instance_of(Ci::ArchiveTraceService)
+ .not_to receive(:execute)
subject
end
diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
index 23f5dda298a..478fb7d2c0f 100644
--- a/spec/workers/ci/archive_traces_cron_worker_spec.rb
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -30,6 +30,13 @@ describe Ci::ArchiveTracesCronWorker do
it_behaves_like 'archives trace'
+ it 'executes service' do
+ expect_any_instance_of(Ci::ArchiveTraceService)
+ .to receive(:execute).with(build)
+
+ subject
+ end
+
context 'when a trace had already been archived' do
let!(:build) { create(:ci_build, :success, :trace_live, :trace_artifact) }
let!(:build2) { create(:ci_build, :success, :trace_live) }
@@ -46,11 +53,12 @@ describe Ci::ArchiveTracesCronWorker do
let!(:build) { create(:ci_build, :success, :trace_live) }
before do
+ allow(Gitlab::Sentry).to receive(:track_exception)
allow_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!).and_raise('Unexpected error')
end
it 'puts a log' do
- expect(Rails.logger).to receive(:error).with("Failed to archive stale live trace. id: #{build.id} message: Unexpected error")
+ expect(Rails.logger).to receive(:error).with("Failed to archive trace. id: #{build.id} message: Unexpected error")
subject
end
diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
index 241e8a2b6d3..d85a87f2cb0 100644
--- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
@@ -58,14 +58,16 @@ describe Gitlab::GithubImport::StageMethods do
end
describe '#find_project' do
+ let(:import_state) { create(:import_state, project: project) }
+
it 'returns a Project for an existing ID' do
- project.update_column(:import_status, 'started')
+ import_state.update_column(:status, 'started')
expect(worker.find_project(project.id)).to eq(project)
end
it 'returns nil for a project that failed importing' do
- project.update_column(:import_status, 'failed')
+ import_state.update_column(:status, 'failed')
expect(worker.find_project(project.id)).to be_nil
end
diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb
index b6c111df8b9..3699fd83a9a 100644
--- a/spec/workers/concerns/project_import_options_spec.rb
+++ b/spec/workers/concerns/project_import_options_spec.rb
@@ -28,13 +28,23 @@ describe ProjectImportOptions do
worker_class.sidekiq_retries_exhausted_block.call(job)
- expect(project.reload.import_error).to include("fork")
+ expect(project.import_state.reload.last_error).to include("fork")
end
it 'logs the appropriate error message for forked projects' do
worker_class.sidekiq_retries_exhausted_block.call(job)
- expect(project.reload.import_error).to include("import")
+ expect(project.import_state.reload.last_error).to include("import")
+ end
+
+ context 'when project does not have import_state' do
+ let(:project) { create(:project) }
+
+ it 'raises an error' do
+ expect do
+ worker_class.sidekiq_retries_exhausted_block.call(job)
+ end.to raise_error(NoMethodError)
+ end
end
end
end
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 2106959e23c..ebe02373275 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -9,7 +9,7 @@ describe 'Every Sidekiq worker' do
expect(Gitlab::SidekiqConfig.cron_workers.map(&:queue)).to all(start_with('cronjob:'))
end
- it 'has its queue in app/workers/all_queues.yml', :aggregate_failures do
+ it 'has its queue in Gitlab::SidekiqConfig::QUEUE_CONFIG_PATHS', :aggregate_failures do
file_worker_queues = Gitlab::SidekiqConfig.worker_queues.to_set
worker_queues = Gitlab::SidekiqConfig.workers.map(&:queue).to_set
@@ -17,10 +17,10 @@ describe 'Every Sidekiq worker' do
worker_queues << 'default'
missing_from_file = worker_queues - file_worker_queues
- expect(missing_from_file).to be_empty, "expected #{missing_from_file.to_a.inspect} to be in app/workers/all_queues.yml"
+ expect(missing_from_file).to be_empty, "expected #{missing_from_file.to_a.inspect} to be in Gitlab::SidekiqConfig::QUEUE_CONFIG_PATHS"
unncessarily_in_file = file_worker_queues - worker_queues
- expect(unncessarily_in_file).to be_empty, "expected #{unncessarily_in_file.to_a.inspect} not to be in app/workers/all_queues.yml"
+ expect(unncessarily_in_file).to be_empty, "expected #{unncessarily_in_file.to_a.inspect} not to be in Gitlab::SidekiqConfig::QUEUE_CONFIG_PATHS"
end
it 'has its queue or namespace in config/sidekiq_queues.yml', :aggregate_failures do
diff --git a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
index 0f78c5cc644..fc7aafbc0c9 100644
--- a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
@@ -17,8 +17,8 @@ describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_st
context 'when there are remaining jobs' do
before do
allow(worker)
- .to receive(:find_project)
- .and_return(project)
+ .to receive(:find_import_state)
+ .and_return(import_state)
end
it 'reschedules itself' do
@@ -38,8 +38,8 @@ describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_st
context 'when there are no remaining jobs' do
before do
allow(worker)
- .to receive(:find_project)
- .and_return(project)
+ .to receive(:find_import_state)
+ .and_return(import_state)
allow(worker)
.to receive(:wait_for_jobs)
@@ -48,8 +48,8 @@ describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_st
end
it 'schedules the next stage' do
- expect(project)
- .to receive(:refresh_import_jid_expiration)
+ expect(import_state)
+ .to receive(:refresh_jid_expiration)
expect(Gitlab::GithubImport::Stage::FinishImportWorker)
.to receive(:perform_async)
@@ -96,22 +96,18 @@ describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_st
end
end
- describe '#find_project' do
- it 'returns a Project' do
- project.update_column(:import_status, 'started')
+ describe '#find_import_state' do
+ it 'returns a ProjectImportState' do
+ import_state.update_column(:status, 'started')
- found = worker.find_project(project.id)
+ found = worker.find_import_state(project.id)
- expect(found).to be_an_instance_of(Project)
-
- # This test is there to make sure we only select the columns we care
- # about.
- # TODO: enable this assertion back again
- # expect(found.attributes).to include({ 'id' => nil, 'import_jid' => '123' })
+ expect(found).to be_an_instance_of(ProjectImportState)
+ expect(found.attributes.keys).to match_array(%w(id jid))
end
it 'returns nil if the project import is not running' do
- expect(worker.find_project(project.id)).to be_nil
+ expect(worker.find_import_state(project.id)).to be_nil
end
end
end
diff --git a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
index 25ada575a44..7ff133f1049 100644
--- a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
@@ -29,7 +29,7 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
context 'when the job is running' do
it 'refreshes the import JID and reschedules itself' do
allow(worker)
- .to receive(:find_project)
+ .to receive(:find_import_state)
.with(project.id)
.and_return(project)
@@ -39,7 +39,7 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
.and_return(true)
expect(project)
- .to receive(:refresh_import_jid_expiration)
+ .to receive(:refresh_jid_expiration)
expect(worker.class)
.to receive(:perform_in_the_future)
@@ -52,7 +52,7 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
context 'when the job is no longer running' do
it 'returns' do
allow(worker)
- .to receive(:find_project)
+ .to receive(:find_import_state)
.with(project.id)
.and_return(project)
@@ -62,18 +62,18 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
.and_return(false)
expect(project)
- .not_to receive(:refresh_import_jid_expiration)
+ .not_to receive(:refresh_jid_expiration)
worker.perform(project.id, '123')
end
end
end
- describe '#find_project' do
- it 'returns a Project' do
+ describe '#find_import_state' do
+ it 'returns a ProjectImportState' do
project = create(:project, :import_started)
- expect(worker.find_project(project.id)).to be_an_instance_of(Project)
+ expect(worker.find_import_state(project.id)).to be_an_instance_of(ProjectImportState)
end
# it 'only selects the import JID field' do
@@ -84,14 +84,14 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
# .to eq({ 'id' => nil, 'import_jid' => '123abc' })
# end
- it 'returns nil for a project for which the import process failed' do
+ it 'returns nil for a import state for which the import process failed' do
project = create(:project, :import_failed)
- expect(worker.find_project(project.id)).to be_nil
+ expect(worker.find_import_state(project.id)).to be_nil
end
- it 'returns nil for a non-existing project' do
- expect(worker.find_project(-1)).to be_nil
+ it 'returns nil for a non-existing find_import_state' do
+ expect(worker.find_import_state(-1)).to be_nil
end
end
end
diff --git a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
index 8c80d660287..ad6154cc4a4 100644
--- a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
let(:project) { create(:project) }
+ let(:import_state) { create(:import_state, project: project) }
let(:worker) { described_class.new }
describe '#import' do
@@ -18,7 +19,7 @@ describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
expect(importer).to receive(:execute)
end
- expect(project).to receive(:refresh_import_jid_expiration)
+ expect(import_state).to receive(:refresh_jid_expiration)
expect(Gitlab::GithubImport::Stage::ImportPullRequestsWorker)
.to receive(:perform_async)
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
index 2fc91a3e80a..1fbb073a34a 100644
--- a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
let(:project) { create(:project) }
+ let(:import_state) { create(:import_state, project: project) }
let(:worker) { described_class.new }
describe '#import' do
@@ -19,8 +20,8 @@ describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
.to receive(:execute)
.and_return(waiter)
- expect(project)
- .to receive(:refresh_import_jid_expiration)
+ expect(import_state)
+ .to receive(:refresh_jid_expiration)
expect(Gitlab::GithubImport::AdvanceStageWorker)
.to receive(:perform_async)
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index d07e40377d4..87ac4bc05c1 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -9,13 +9,13 @@ describe RepositoryImportWorker do
describe '#perform' do
let(:project) { create(:project, :import_scheduled) }
+ let(:import_state) { project.import_state }
context 'when worker was reset without cleanup' do
it 'imports the project successfully' do
jid = '12345678'
started_project = create(:project)
-
- create(:import_state, :started, project: started_project, jid: jid)
+ started_import_state = create(:import_state, :started, project: started_project, jid: jid)
allow(subject).to receive(:jid).and_return(jid)
@@ -23,12 +23,12 @@ describe RepositoryImportWorker do
.and_return({ status: :ok })
# Works around https://github.com/rspec/rspec-mocks/issues/910
- expect(Project).to receive(:find).with(project.id).and_return(project)
- expect(project.repository).to receive(:expire_emptiness_caches)
- expect(project.wiki.repository).to receive(:expire_emptiness_caches)
- expect(project).to receive(:import_finish)
+ expect(Project).to receive(:find).with(started_project.id).and_return(started_project)
+ expect(started_project.repository).to receive(:expire_emptiness_caches)
+ expect(started_project.wiki.repository).to receive(:expire_emptiness_caches)
+ expect(started_import_state).to receive(:finish)
- subject.perform(project.id)
+ subject.perform(started_project.id)
end
end
@@ -41,7 +41,7 @@ describe RepositoryImportWorker do
expect(Project).to receive(:find).with(project.id).and_return(project)
expect(project.repository).to receive(:expire_emptiness_caches)
expect(project.wiki.repository).to receive(:expire_emptiness_caches)
- expect(project).to receive(:import_finish)
+ expect(import_state).to receive(:finish)
subject.perform(project.id)
end
@@ -51,26 +51,27 @@ describe RepositoryImportWorker do
it 'hide the credentials that were used in the import URL' do
error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
- project.update(import_jid: '123')
+ import_state.update(jid: '123')
expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error })
expect do
subject.perform(project.id)
end.to raise_error(RuntimeError, error)
- expect(project.reload.import_jid).not_to be_nil
+ expect(import_state.reload.jid).not_to be_nil
end
it 'updates the error on Import/Export' do
error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
- project.update(import_jid: '123', import_type: 'gitlab_project')
+ project.update(import_type: 'gitlab_project')
+ import_state.update(jid: '123')
expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error })
expect do
subject.perform(project.id)
end.to raise_error(RuntimeError, error)
- expect(project.reload.import_error).not_to be_nil
+ expect(import_state.reload.last_error).not_to be_nil
end
end
@@ -90,8 +91,8 @@ describe RepositoryImportWorker do
.to receive(:async?)
.and_return(true)
- expect_any_instance_of(Project)
- .not_to receive(:import_finish)
+ expect_any_instance_of(ProjectImportState)
+ .not_to receive(:finish)
subject.perform(project.id)
end
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index 557934346c9..e09b8e5b964 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -5,7 +5,7 @@ describe StuckCiJobsWorker do
let!(:runner) { create :ci_runner }
let!(:job) { create :ci_build, runner: runner }
- let(:trace_lease_key) { "trace:archive:#{job.id}" }
+ let(:trace_lease_key) { "trace:write:lock:#{job.id}" }
let(:trace_lease_uuid) { SecureRandom.uuid }
let(:worker_lease_key) { StuckCiJobsWorker::EXCLUSIVE_LEASE_KEY }
let(:worker_lease_uuid) { SecureRandom.uuid }